{"thread":{"id":"61578","subject":"[PATCH 00/29] Memory leak fixes (pt.2)","startedAt":"2024-06-03T09:46:28Z","lastAt":"2024-06-12T09:35:17Z","messageCount":74,"participants":["Patrick Steinhardt","Phillip Wood","Karthik Nayak"],"isPatch":true,"patchVersion":1,"patchTotal":29},"messages":[{"id":"496117","messageId":"cover.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":null,"subject":"[PATCH 00/29] Memory leak fixes (pt.2)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:23Z","receivedAt":"2024-06-03T09:46:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Hi,\n\nthis is another random assortment of memory leak fixes for Git. With\nthese changes, another 77 test suites start to pass with the leak\nsanitizer enabled. With that, we're down to ~270 test suites that do not\nyet pass with the sanitizer enabled. My goal is that we can reduce this\nnumber to 0 this year -- any help here would be appreciated.\n\nWhile most of the commits are trivial, I realize that the overall series\nis quite large. If you think that this is too big, please feel free to\nspeak up up and I'll split this and future series into batches of at\nmost 20 patches or less, if you think that's still too many.\n\nThe series is built on top of 9eaef5822c (Sync with 'maint', 2024-05-31)\nwith ps/leakfixes at 164937678c (Merge remote-tracking branch\n'junio/ps/leakfixes' into HEAD, 2024-06-03) merged into it.\n\nThanks!\n\nPatrick Steinhardt (29):\n  revision: fix memory leak when reversing revisions\n  parse-options: fix leaks for users of OPT_FILENAME\n  notes-utils: free note trees when releasing copied notes\n  bundle: plug leaks in `create_bundle()`\n  biultin/rev-parse: fix memory leaks in `--parseopt` mode\n  merge-recursive: fix leaging rename conflict info\n  revision: fix leaking display notes\n  notes: fix memory leak when pruning notes\n  builtin/rev-list: fix leaking bitmap index when calculating disk usage\n  object-name: free leaking object contexts\n  builtin/difftool: plug memory leaks in `run_dir_diff()`\n  builtin/merge-recursive: fix leaking object ID bases\n  merge-recursive: fix memory leak when finalizing merge\n  builtin/log: fix leaking commit list in git-cherry(1)\n  revision: free diff options\n  builtin/stash: fix leak in `show_stash()`\n  rerere: fix various trivial leaks\n  config: fix leaking \"core.notesref\" variable\n  commit: fix leaking parents when calling `commit_tree_extended()`\n  sequencer: fix leaking string buffer in `commit_staged_changes()`\n  apply: fix leaking string in `match_fragment()`\n  builtin/clone: plug leaking HEAD ref in `wanted_peer_refs()`\n  sequencer: fix memory leaks in `make_script_with_merges()`\n  builtin/merge: fix leaking `struct cmdnames` in `get_strategy()`\n  merge: fix leaking merge bases\n  line-range: plug leaking find functions\n  blame: fix leaking data for blame scoreboards\n  builtin/blame: fix leaking prefixed paths\n  builtin/blame: fix leaking ignore revs files\n\n apply.c                                   |  88 ++++++++++-------\n apply.h                                   |   2 +-\n blame.c                                   |   4 +\n builtin/am.c                              |   7 +-\n builtin/archive.c                         |   7 +-\n builtin/blame.c                           |   8 +-\n builtin/cat-file.c                        |  17 ++--\n builtin/clone.c                           |   3 +-\n builtin/commit-tree.c                     |  11 ++-\n builtin/commit.c                          |  10 +-\n builtin/difftool.c                        |   3 +\n builtin/fmt-merge-msg.c                   |   4 +-\n builtin/grep.c                            |   4 +-\n builtin/log.c                             |  16 ++--\n builtin/ls-tree.c                         |   3 +-\n builtin/merge-recursive.c                 |   6 +-\n builtin/merge-tree.c                      |   1 +\n builtin/merge.c                           |  18 +++-\n builtin/multi-pack-index.c                |  13 ++-\n builtin/replay.c                          |  14 ++-\n builtin/rev-list.c                        |   2 +\n builtin/rev-parse.c                       |  55 ++++++-----\n builtin/shortlog.c                        |   5 +-\n builtin/sparse-checkout.c                 |   1 +\n builtin/stash.c                           |  23 +++--\n bundle.c                                  |  29 ++++--\n commit.c                                  |  28 +++---\n commit.h                                  |  12 +--\n config.c                                  |   1 +\n diff-lib.c                                |   2 +\n diff.c                                    |   8 +-\n help.c                                    |  12 +--\n help.h                                    |   2 +\n line-range.c                              |   2 +\n list-objects-filter.c                     |   2 +\n log-tree.c                                |   1 +\n merge-ort-wrappers.c                      |   2 +-\n merge-ort-wrappers.h                      |   2 +-\n merge-ort.c                               |  12 ++-\n merge-ort.h                               |   2 +-\n merge-recursive.c                         |  68 ++++++++-----\n merge-recursive.h                         |   4 +-\n notes-merge.c                             |   1 +\n notes-utils.c                             |   9 +-\n notes-utils.h                             |   2 +-\n notes.c                                   |  21 +++--\n notes.h                                   |   5 +\n object-name.c                             |  40 +++++---\n object-name.h                             |   2 +\n rerere.c                                  |   3 +\n revision.c                                |  59 +++++++-----\n sequencer.c                               | 110 +++++++++++++++-------\n t/helper/test-parse-options.c             |   1 +\n t/t1004-read-tree-m-u-wf.sh               |   1 +\n t/t1015-read-index-unmerged.sh            |   2 +\n t/t1021-rerere-in-workdir.sh              |   1 +\n t/t1512-rev-parse-disambiguation.sh       |   1 +\n t/t2500-untracked-overwriting.sh          |   1 +\n t/t3301-notes.sh                          |   1 +\n t/t3306-notes-prune.sh                    |   1 +\n t/t3308-notes-merge.sh                    |   1 +\n t/t3309-notes-merge-auto-resolve.sh       |   1 +\n t/t3400-rebase.sh                         |   1 +\n t/t3401-rebase-and-am-rename.sh           |   1 +\n t/t3403-rebase-skip.sh                    |   1 +\n t/t3406-rebase-message.sh                 |   1 +\n t/t3407-rebase-abort.sh                   |   1 +\n t/t3417-rebase-whitespace-fix.sh          |   1 +\n t/t3418-rebase-continue.sh                |   1 +\n t/t3420-rebase-autostash.sh               |   1 +\n t/t3421-rebase-topology-linear.sh         |   2 +\n t/t3424-rebase-empty.sh                   |   1 +\n t/t3428-rebase-signoff.sh                 |   1 +\n t/t3430-rebase-merges.sh                  |   1 +\n t/t3434-rebase-i18n.sh                    |   1 +\n t/t3500-cherry.sh                         |   1 +\n t/t3504-cherry-pick-rerere.sh             |   1 +\n t/t3505-cherry-pick-empty.sh              |   1 +\n t/t3508-cherry-pick-many-commits.sh       |   1 +\n t/t3509-cherry-pick-merge-df.sh           |   1 +\n t/t3907-stash-show-config.sh              |   1 +\n t/t4061-diff-indent.sh                    |   1 +\n t/t4131-apply-fake-ancestor.sh            |   1 +\n t/t4151-am-abort.sh                       |   1 +\n t/t4153-am-resume-override-opts.sh        |   1 +\n t/t4208-log-magic-pathspec.sh             |   1 +\n t/t4253-am-keep-cr-dos.sh                 |   1 +\n t/t4255-am-submodule.sh                   |   1 +\n t/t5150-request-pull.sh                   |   1 +\n t/t5300-pack-object.sh                    |   4 +-\n t/t5305-include-tag.sh                    |   1 +\n t/t5407-post-rewrite-hook.sh              |   1 +\n t/t5605-clone-local.sh                    |   1 +\n t/t5607-clone-bundle.sh                   |   1 +\n t/t5612-clone-refspec.sh                  |   1 +\n t/t6000-rev-list-misc.sh                  |   1 +\n t/t6001-rev-list-graft.sh                 |   1 +\n t/t6013-rev-list-reverse-parents.sh       |   1 +\n t/t6017-rev-list-stdin.sh                 |   1 +\n t/t6020-bundle-misc.sh                    |   1 +\n t/t6115-rev-list-du.sh                    |   2 +\n t/t6130-pathspec-noglob.sh                |   2 +\n t/t6402-merge-rename.sh                   |   1 +\n t/t6427-diff3-conflict-markers.sh         |   1 +\n t/t6430-merge-recursive.sh                |   1 +\n t/t6432-merge-recursive-space-options.sh  |   1 +\n t/t6434-merge-recursive-rename-options.sh |   1 +\n t/t6436-merge-overwrite.sh                |   1 +\n t/t7006-pager.sh                          |   1 +\n t/t7010-setup.sh                          |   1 +\n t/t7012-skip-worktree-writing.sh          |   1 +\n t/t7201-co.sh                             |   1 +\n t/t7501-commit-basic-functionality.sh     |   1 +\n t/t7505-prepare-commit-msg-hook.sh        |   1 +\n t/t7512-status-help.sh                    |   1 +\n t/t7600-merge.sh                          |   1 +\n t/t7606-merge-custom.sh                   |   1 +\n t/t7611-merge-abort.sh                    |   1 +\n t/t8002-blame.sh                          |   1 +\n t/t8003-blame-corner-cases.sh             |   1 +\n t/t8004-blame-with-conflicts.sh           |   1 +\n t/t8006-blame-textconv.sh                 |   2 +\n t/t8008-blame-formats.sh                  |   2 +\n t/t8009-blame-vs-topicbranches.sh         |   2 +\n t/t8011-blame-split-file.sh               |   2 +\n t/t8012-blame-colors.sh                   |   1 +\n t/t8013-blame-ignore-revs.sh              |   2 +\n t/t8014-blame-ignore-fuzzy.sh             |   2 +\n t/t9500-gitweb-standalone-no-errors.sh    |   1 +\n t/t9502-gitweb-standalone-parse-output.sh |   1 +\n 130 files changed, 588 insertions(+), 269 deletions(-)\n\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496118","messageId":"a88a6fa266696e1b7ce0a5a10edd04d0c52ea912.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 01/29] revision: fix memory leak when reversing revisions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:28Z","receivedAt":"2024-06-03T09:46:32Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When reversing revisions in a rev walk, `get_revision()` will allocate a\nnew commit list and assign it to `revs->commits`. It does not free the\nold list though, which makes it leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c                          | 1 +\n t/t3508-cherry-pick-many-commits.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..af95502d92 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -4430,6 +4430,7 @@ struct commit *get_revision(struct rev_info *revs)\n \t\treversed = NULL;\n \t\twhile ((c = get_revision_internal(revs)))\n \t\t\tcommit_list_insert(c, &reversed);\n+\t\tfree_commit_list(revs->commits);\n \t\trevs->commits = reversed;\n \t\trevs->reverse = 0;\n \t\trevs->reverse_output_stage = 1;\ndiff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh\nindex 2d53ce754c..afa7727a4a 100755\n--- a/t/t3508-cherry-pick-many-commits.sh\n+++ b/t/t3508-cherry-pick-many-commits.sh\n@@ -5,6 +5,7 @@ test_description='test cherry-picking many commits'\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_head_differs_from() {\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496119","messageId":"ecabbb74e19ea773fd23719bd7fd4937b29679e5.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 02/29] parse-options: fix leaks for users of OPT_FILENAME","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:33Z","receivedAt":"2024-06-03T09:46:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `OPT_FILENAME()` option will, if set, put an allocated string into\nthe user-provided variable. Consequently, that variable thus needs to be\nfree'd by the caller of `parse_options()`. Some callsites don't though\nand thus leak memory. Fix those.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n apply.c                             |  1 +\n apply.h                             |  2 +-\n builtin/archive.c                   |  7 +++++--\n builtin/commit.c                    |  7 +++++--\n builtin/fmt-merge-msg.c             |  4 +++-\n builtin/log.c                       |  4 +++-\n builtin/multi-pack-index.c          | 13 +++++++++----\n builtin/sparse-checkout.c           |  1 +\n t/helper/test-parse-options.c       |  1 +\n t/t1512-rev-parse-disambiguation.sh |  1 +\n t/t2500-untracked-overwriting.sh    |  1 +\n t/t3406-rebase-message.sh           |  1 +\n t/t3407-rebase-abort.sh             |  1 +\n t/t3428-rebase-signoff.sh           |  1 +\n t/t4131-apply-fake-ancestor.sh      |  1 +\n t/t4151-am-abort.sh                 |  1 +\n t/t4253-am-keep-cr-dos.sh           |  1 +\n t/t4255-am-submodule.sh             |  1 +\n t/t5407-post-rewrite-hook.sh        |  1 +\n t/t6427-diff3-conflict-markers.sh   |  1 +\n t/t7512-status-help.sh              |  1 +\n 21 files changed, 41 insertions(+), 11 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 901b67e625..d8d26a48f1 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -135,6 +135,7 @@ void clear_apply_state(struct apply_state *state)\n \tstrset_clear(&state->removed_symlinks);\n \tstrset_clear(&state->kept_symlinks);\n \tstrbuf_release(&state->root);\n+\tFREE_AND_NULL(state->fake_ancestor);\n \n \t/* &state->fn_table is cleared at the end of apply_patch() */\n }\ndiff --git a/apply.h b/apply.h\nindex 7cd38b1443..36d7c3f70b 100644\n--- a/apply.h\n+++ b/apply.h\n@@ -59,7 +59,7 @@ struct apply_state {\n \tstruct repository *repo;\n \tconst char *index_file;\n \tenum apply_verbosity apply_verbosity;\n-\tconst char *fake_ancestor;\n+\tchar *fake_ancestor;\n \tconst char *patch_input_file;\n \tint line_termination;\n \tstruct strbuf root;\ndiff --git a/builtin/archive.c b/builtin/archive.c\nindex 15ee1ec7bb..f29c0ef6ad 100644\n--- a/builtin/archive.c\n+++ b/builtin/archive.c\n@@ -92,6 +92,7 @@ int cmd_archive(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"path to the remote git-upload-archive command\")),\n \t\tOPT_END()\n \t};\n+\tint ret;\n \n \targc = parse_options(argc, argv, prefix, local_opts, NULL,\n \t\t\t     PARSE_OPT_KEEP_ALL);\n@@ -106,6 +107,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix)\n \n \tsetvbuf(stderr, NULL, _IOLBF, BUFSIZ);\n \n-\tUNLEAK(output);\n-\treturn write_archive(argc, argv, prefix, the_repository, output, 0);\n+\tret = write_archive(argc, argv, prefix, the_repository, output, 0);\n+\n+\tfree(output);\n+\treturn ret;\n }\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..dcaf4efa03 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -106,7 +106,8 @@ static enum {\n \tCOMMIT_PARTIAL\n } commit_style;\n \n-static const char *logfile, *force_author;\n+static const char *force_author;\n+static char *logfile;\n static char *template_file;\n /*\n  * The _message variables are commit names from which to take\n@@ -1309,7 +1310,7 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t\t\t\t  !!use_message, \"-C\",\n \t\t\t\t  !!logfile, \"-F\");\n \tif (use_message || edit_message || logfile ||fixup_message || have_option_m)\n-\t\ttemplate_file = NULL;\n+\t\tFREE_AND_NULL(template_file);\n \tif (edit_message)\n \t\tuse_message = edit_message;\n \tif (amend && !use_message && !fixup_message)\n@@ -1892,5 +1893,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&author_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\n+\tfree(logfile);\n+\tfree(template_file);\n \treturn ret;\n }\ndiff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c\nindex 0f9855b680..957786d1b3 100644\n--- a/builtin/fmt-merge-msg.c\n+++ b/builtin/fmt-merge-msg.c\n@@ -11,7 +11,7 @@ static const char * const fmt_merge_msg_usage[] = {\n \n int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *inpath = NULL;\n+\tchar *inpath = NULL;\n \tconst char *message = NULL;\n \tchar *into_name = NULL;\n \tint shortlog_len = -1;\n@@ -66,5 +66,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)\n \tif (ret)\n \t\treturn ret;\n \twrite_in_full(STDOUT_FILENO, output.buf, output.len);\n+\n+\tfree(inpath);\n \treturn 0;\n }\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..4e4b645a21 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -2021,7 +2021,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tconst char *rfc = NULL;\n \tint creation_factor = -1;\n \tconst char *signature = git_version_string;\n-\tconst char *signature_file_arg = NULL;\n+\tchar *signature_file_arg = NULL;\n \tstruct keep_callback_data keep_callback_data = {\n \t\t.cfg = &cfg,\n \t\t.revs = &rev,\n@@ -2559,6 +2559,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&rdiff1);\n \tstrbuf_release(&rdiff2);\n \tstrbuf_release(&rdiff_title);\n+\tfree(description_file);\n+\tfree(signature_file_arg);\n \tfree(to_free);\n \tfree(rev.message_id);\n \tif (rev.ref_message_ids)\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 8360932d2e..9cf1a32d65 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -50,7 +50,7 @@ static char const * const builtin_multi_pack_index_usage[] = {\n static struct opts_multi_pack_index {\n \tchar *object_dir;\n \tconst char *preferred_pack;\n-\tconst char *refs_snapshot;\n+\tchar *refs_snapshot;\n \tunsigned long batch_size;\n \tunsigned flags;\n \tint stdin_packs;\n@@ -135,6 +135,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \t\t\t     N_(\"refs snapshot for selecting bitmap commits\")),\n \t\tOPT_END(),\n \t};\n+\tint ret;\n \n \topts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;\n \n@@ -157,7 +158,6 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \tif (opts.stdin_packs) {\n \t\tstruct string_list packs = STRING_LIST_INIT_DUP;\n-\t\tint ret;\n \n \t\tread_packs_from_stdin(&packs);\n \n@@ -166,12 +166,17 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \t\t\t\t\t   opts.refs_snapshot, opts.flags);\n \n \t\tstring_list_clear(&packs, 0);\n+\t\tfree(opts.refs_snapshot);\n \n \t\treturn ret;\n \n \t}\n-\treturn write_midx_file(opts.object_dir, opts.preferred_pack,\n-\t\t\t       opts.refs_snapshot, opts.flags);\n+\n+\tret = write_midx_file(opts.object_dir, opts.preferred_pack,\n+\t\t\t      opts.refs_snapshot, opts.flags);\n+\n+\tfree(opts.refs_snapshot);\n+\treturn ret;\n }\n \n static int cmd_multi_pack_index_verify(int argc, const char **argv,\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 0f52e25249..84a6adf681 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -1011,6 +1011,7 @@ static int sparse_checkout_check_rules(int argc, const char **argv, const char *\n \n \tret = check_rules(&pl, check_rules_opts.null_termination);\n \tclear_pattern_list(&pl);\n+\tfree(check_rules_opts.rules_file);\n \treturn ret;\n }\n \ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex ded8116cc5..5250913d99 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -207,6 +207,7 @@ int cmd__parse_options(int argc, const char **argv)\n \texpect.strdup_strings = 1;\n \tstring_list_clear(&expect, 0);\n \tstring_list_clear(&list, 0);\n+\tfree(file);\n \n \treturn ret;\n }\ndiff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh\nindex 70f1e0a998..f9d68ce74e 100755\n--- a/t/t1512-rev-parse-disambiguation.sh\n+++ b/t/t1512-rev-parse-disambiguation.sh\n@@ -23,6 +23,7 @@ one tagged as v1.0.0.  They all have one regular file each.\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 test_cmp_failed_rev_parse () {\ndiff --git a/t/t2500-untracked-overwriting.sh b/t/t2500-untracked-overwriting.sh\nindex 5c0bf4d21f..714feb83be 100755\n--- a/t/t2500-untracked-overwriting.sh\n+++ b/t/t2500-untracked-overwriting.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test handling of overwriting untracked files'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_setup_reset () {\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex a1d7fa7f7c..82108b67e6 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -5,6 +5,7 @@ test_description='messages from rebase operation'\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 test_expect_success 'setup' '\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 9f49c4228b..2c3f38d45a 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --abort tests'\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 test_expect_success setup '\ndiff --git a/t/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh\nindex 6f57aed9fa..365436ebfc 100755\n--- a/t/t3428-rebase-signoff.sh\n+++ b/t/t3428-rebase-signoff.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --signoff\n This test runs git rebase --signoff and make sure that it works.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-rebase.sh\n \ndiff --git a/t/t4131-apply-fake-ancestor.sh b/t/t4131-apply-fake-ancestor.sh\nindex b1361ce546..40c92115a6 100755\n--- a/t/t4131-apply-fake-ancestor.sh\n+++ b/t/t4131-apply-fake-ancestor.sh\n@@ -5,6 +5,7 @@\n \n test_description='git apply --build-fake-ancestor handling.'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh\nindex edb38da701..1825a89d6a 100755\n--- a/t/t4151-am-abort.sh\n+++ b/t/t4151-am-abort.sh\n@@ -2,6 +2,7 @@\n \n test_description='am --abort'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t4253-am-keep-cr-dos.sh b/t/t4253-am-keep-cr-dos.sh\nindex 0ee69d2a0c..2bcdd9f34f 100755\n--- a/t/t4253-am-keep-cr-dos.sh\n+++ b/t/t4253-am-keep-cr-dos.sh\n@@ -9,6 +9,7 @@ test_description='git-am mbox with dos line ending.\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 # Three patches which will be added as files with dos line ending.\ndiff --git a/t/t4255-am-submodule.sh b/t/t4255-am-submodule.sh\nindex a7ba08f728..04f3ccfc41 100755\n--- a/t/t4255-am-submodule.sh\n+++ b/t/t4255-am-submodule.sh\n@@ -2,6 +2,7 @@\n \n test_description='git am handling submodules'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-submodule-update.sh\n \ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex ad7f8c6f00..e99e728236 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -7,6 +7,7 @@ test_description='Test the post-rewrite hook.'\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 test_expect_success 'setup' '\ndiff --git a/t/t6427-diff3-conflict-markers.sh b/t/t6427-diff3-conflict-markers.sh\nindex dd5fe6a402..a13271b349 100755\n--- a/t/t6427-diff3-conflict-markers.sh\n+++ b/t/t6427-diff3-conflict-markers.sh\n@@ -5,6 +5,7 @@ test_description='recursive merge diff3 style conflict markers'\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 # Setup:\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 802f8f704c..cdd5f2c697 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -10,6 +10,7 @@ test_description='git status advice'\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496120","messageId":"17a8f532752e005601b1cff5690a4dae16096b8b.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 03/29] notes-utils: free note trees when releasing copied notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:37Z","receivedAt":"2024-06-03T09:46:42Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we clear most of the members of `struct notet_rewrite_cfg` in\n`finish_copy_notes_for_rewrite()`, we do not clear the notes tree. Fix\nthis to plug this memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n notes-utils.c                         | 1 +\n t/t3400-rebase.sh                     | 1 +\n t/t7501-commit-basic-functionality.sh | 1 +\n 3 files changed, 3 insertions(+)\n\ndiff --git a/notes-utils.c b/notes-utils.c\nindex e33aa86c4b..671d1969b1 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -187,6 +187,7 @@ void finish_copy_notes_for_rewrite(struct repository *r,\n \tfor (i = 0; c->trees[i]; i++) {\n \t\tcommit_notes(r, c->trees[i], msg);\n \t\tfree_notes(c->trees[i]);\n+\t\tfree(c->trees[i]);\n \t}\n \tfree(c->trees);\n \tfree(c);\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex e1c8c5f701..ae34bfad60 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -11,6 +11,7 @@ among other things.\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 GIT_AUTHOR_NAME=author@name\ndiff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh\nindex cc12f99f11..52f5e28154 100755\n--- a/t/t7501-commit-basic-functionality.sh\n+++ b/t/t7501-commit-basic-functionality.sh\n@@ -9,6 +9,7 @@ test_description='git commit'\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 . \"$TEST_DIRECTORY/lib-diff.sh\"\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496121","messageId":"0c8c97cded4b5070f9c1d4b35729636279f87b96.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 04/29] bundle: plug leaks in `create_bundle()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:42Z","receivedAt":"2024-06-03T09:46:46Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When creating a bundle, we set up a revision walk, but never release\ndata associated with it. Furthermore, we create a mostly-shallow copy of\nthat revision walk where we only adapt its pending objects such that we\ncan reuse the walk. While that copy must not be released, the pending\nobjects array need to be.\n\nPlug those memory leaks by releasing the revision walk and the pending\nobjects of the copied revision walk.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bundle.c                | 29 +++++++++++++++++++----------\n t/t5605-clone-local.sh  |  1 +\n t/t5607-clone-bundle.sh |  1 +\n t/t6020-bundle-misc.sh  |  1 +\n 4 files changed, 22 insertions(+), 10 deletions(-)\n\ndiff --git a/bundle.c b/bundle.c\nindex 95367c2d0a..9af558c7a9 100644\n--- a/bundle.c\n+++ b/bundle.c\n@@ -500,6 +500,7 @@ int create_bundle(struct repository *r, const char *path,\n \tstruct rev_info revs, revs_copy;\n \tint min_version = 2;\n \tstruct bundle_prerequisites_info bpi;\n+\tint ret;\n \tint i;\n \n \t/* init revs to list objects for pack-objects later */\n@@ -525,8 +526,8 @@ int create_bundle(struct repository *r, const char *path,\n \t\tmin_version = 3;\n \n \tif (argc > 1) {\n-\t\terror(_(\"unrecognized argument: %s\"), argv[1]);\n-\t\tgoto err;\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n+\t\tgoto out;\n \t}\n \n \tbundle_to_stdout = !strcmp(path, \"-\");\n@@ -591,23 +592,31 @@ int create_bundle(struct repository *r, const char *path,\n \n \t/* write bundle refs */\n \tref_count = write_bundle_refs(bundle_fd, &revs_copy);\n-\tif (!ref_count)\n+\tif (!ref_count) {\n \t\tdie(_(\"Refusing to create empty bundle.\"));\n-\telse if (ref_count < 0)\n-\t\tgoto err;\n+\t} else if (ref_count < 0) {\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n \n \t/* write pack */\n-\tif (write_pack_data(bundle_fd, &revs_copy, pack_options))\n-\t\tgoto err;\n+\tif (write_pack_data(bundle_fd, &revs_copy, pack_options)) {\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n \n \tif (!bundle_to_stdout) {\n \t\tif (commit_lock_file(&lock))\n \t\t\tdie_errno(_(\"cannot create '%s'\"), path);\n \t}\n-\treturn 0;\n-err:\n+\n+\tret = 0;\n+\n+out:\n+\tobject_array_clear(&revs_copy.pending);\n+\trelease_revisions(&revs);\n \trollback_lock_file(&lock);\n-\treturn -1;\n+\treturn ret;\n }\n \n int unbundle(struct repository *r, struct bundle_header *header,\ndiff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh\nindex a3055869bc..9a1390a98f 100755\n--- a/t/t5605-clone-local.sh\n+++ b/t/t5605-clone-local.sh\n@@ -4,6 +4,7 @@ test_description='test local clone'\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 repo_is_hardlinked() {\ndiff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh\nindex 0d1e92d996..ac5ce9b648 100755\n--- a/t/t5607-clone-bundle.sh\n+++ b/t/t5607-clone-bundle.sh\n@@ -4,6 +4,7 @@ test_description='some bundle related tests'\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 test_expect_success 'setup' '\ndiff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh\nindex 3e6bcbf30c..fe75a06572 100755\n--- a/t/t6020-bundle-misc.sh\n+++ b/t/t6020-bundle-misc.sh\n@@ -8,6 +8,7 @@ test_description='Test git-bundle'\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 . \"$TEST_DIRECTORY\"/lib-bundle.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496122","messageId":"620814fb99f4ef51ce9389ec098670b3d167f391.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 05/29] biultin/rev-parse: fix memory leaks in `--parseopt` mode","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:47Z","receivedAt":"2024-06-03T09:46:51Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a bunch of memory leaks in git-rev-parse(1)'s `--parseopt` mode.\nRefactor the code to use `struct strvec`s to make it easier for us to\ntrack the lifecycle of those leaking variables and then free them.\n\nWhile at it, remove the unneeded static lifetime for some of the\nvariables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rev-parse.c     | 53 +++++++++++++++++++++++------------------\n t/t5150-request-pull.sh |  1 +\n t/t7006-pager.sh        |  1 +\n 3 files changed, 32 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 1e2919fd81..ab8a8f3b0e 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -423,12 +423,12 @@ static char *findspace(const char *s)\n \n static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n {\n-\tstatic int keep_dashdash = 0, stop_at_non_option = 0;\n-\tstatic char const * const parseopt_usage[] = {\n+\tint keep_dashdash = 0, stop_at_non_option = 0;\n+\tchar const * const parseopt_usage[] = {\n \t\tN_(\"git rev-parse --parseopt [<options>] -- [<args>...]\"),\n \t\tNULL\n \t};\n-\tstatic struct option parseopt_opts[] = {\n+\tstruct option parseopt_opts[] = {\n \t\tOPT_BOOL(0, \"keep-dashdash\", &keep_dashdash,\n \t\t\t\t\tN_(\"keep the `--` passed as an arg\")),\n \t\tOPT_BOOL(0, \"stop-at-non-option\", &stop_at_non_option,\n@@ -438,12 +438,11 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tN_(\"output in stuck long form\")),\n \t\tOPT_END(),\n \t};\n-\tstatic const char * const flag_chars = \"*=?!\";\n-\n \tstruct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;\n-\tconst char **usage = NULL;\n+\tstruct strvec longnames = STRVEC_INIT;\n+\tstruct strvec usage = STRVEC_INIT;\n \tstruct option *opts = NULL;\n-\tint onb = 0, osz = 0, unb = 0, usz = 0;\n+\tsize_t opts_nr = 0, opts_alloc = 0;\n \n \tstrbuf_addstr(&parsed, \"set --\");\n \targc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,\n@@ -453,16 +452,16 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \n \t/* get the usage up to the first line with a -- on it */\n \tfor (;;) {\n+\t\tstrbuf_reset(&sb);\n \t\tif (strbuf_getline(&sb, stdin) == EOF)\n \t\t\tdie(_(\"premature end of input\"));\n-\t\tALLOC_GROW(usage, unb + 1, usz);\n \t\tif (!strcmp(\"--\", sb.buf)) {\n-\t\t\tif (unb < 1)\n+\t\t\tif (!usage.nr)\n \t\t\t\tdie(_(\"no usage string given before the `--' separator\"));\n-\t\t\tusage[unb] = NULL;\n \t\t\tbreak;\n \t\t}\n-\t\tusage[unb++] = strbuf_detach(&sb, NULL);\n+\n+\t\tstrvec_push(&usage, sb.buf);\n \t}\n \n \t/* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */\n@@ -474,10 +473,10 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \t\tif (!sb.len)\n \t\t\tcontinue;\n \n-\t\tALLOC_GROW(opts, onb + 1, osz);\n-\t\tmemset(opts + onb, 0, sizeof(opts[onb]));\n+\t\tALLOC_GROW(opts, opts_nr + 1, opts_alloc);\n+\t\tmemset(opts + opts_nr, 0, sizeof(*opts));\n \n-\t\to = &opts[onb++];\n+\t\to = &opts[opts_nr++];\n \t\thelp = findspace(sb.buf);\n \t\tif (!help || sb.buf == help) {\n \t\t\to->type = OPTION_GROUP;\n@@ -494,20 +493,22 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \t\to->callback = &parseopt_dump;\n \n \t\t/* name(s) */\n-\t\ts = strpbrk(sb.buf, flag_chars);\n+\t\ts = strpbrk(sb.buf, \"*=?!\");\n \t\tif (!s)\n \t\t\ts = help;\n \n \t\tif (s == sb.buf)\n \t\t\tdie(_(\"missing opt-spec before option flags\"));\n \n-\t\tif (s - sb.buf == 1) /* short option only */\n+\t\tif (s - sb.buf == 1) { /* short option only */\n \t\t\to->short_name = *sb.buf;\n-\t\telse if (sb.buf[1] != ',') /* long option only */\n-\t\t\to->long_name = xmemdupz(sb.buf, s - sb.buf);\n-\t\telse {\n+\t\t} else if (sb.buf[1] != ',') { /* long option only */\n+\t\t\to->long_name = strvec_pushf(&longnames, \"%.*s\",\n+\t\t\t\t\t\t    (int)(s - sb.buf), sb.buf);\n+\t\t} else {\n \t\t\to->short_name = *sb.buf;\n-\t\t\to->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);\n+\t\t\to->long_name = strvec_pushf(&longnames, \"%.*s\",\n+\t\t\t\t\t\t    (int)(s - sb.buf - 2), sb.buf + 2);\n \t\t}\n \n \t\t/* flags */\n@@ -537,9 +538,9 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&sb);\n \n \t/* put an OPT_END() */\n-\tALLOC_GROW(opts, onb + 1, osz);\n-\tmemset(opts + onb, 0, sizeof(opts[onb]));\n-\targc = parse_options(argc, argv, prefix, opts, usage,\n+\tALLOC_GROW(opts, opts_nr + 1, opts_alloc);\n+\tmemset(opts + opts_nr, 0, sizeof(*opts));\n+\targc = parse_options(argc, argv, prefix, opts, usage.v,\n \t\t\t(keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) |\n \t\t\t(stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) |\n \t\t\tPARSE_OPT_SHELL_EVAL);\n@@ -547,7 +548,13 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \tstrbuf_addstr(&parsed, \" --\");\n \tsq_quote_argv(&parsed, argv);\n \tputs(parsed.buf);\n+\n \tstrbuf_release(&parsed);\n+\tstrbuf_release(&sb);\n+\tstrvec_clear(&longnames);\n+\tstrvec_clear(&usage);\n+\tfree((char *) opts->help);\n+\tfree(opts);\n \treturn 0;\n }\n \ndiff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh\nindex cb67bac1c4..86bee33160 100755\n--- a/t/t5150-request-pull.sh\n+++ b/t/t5150-request-pull.sh\n@@ -5,6 +5,7 @@ test_description='Test workflows involving pull request.'\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 if ! test_have_prereq PERL\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex e56ca5b0fa..60e4c90de1 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test automatic use of a pager.'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-pager.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496123","messageId":"0052d60ee889981f2ba92c9781e1659e961eace4.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 06/29] merge-recursive: fix leaging rename conflict info","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:52Z","receivedAt":"2024-06-03T09:46:56Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When computing rename conflicts in our recursive merge algorithm we set\nup `struct rename_conflict_info`s to track that information. We never\nfree those data structures though and thus leak memory.\n\nWe need to be a bit more careful here though because the same rename\nconflict info can be assigned to multiple structures. Accomodate for\nthis by introducing a `rename_conflict_info_owned` bit that we can use\nto steer whether or not the rename conflict info shall be free'd.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-recursive.c                  | 8 +++++++-\n t/t3401-rebase-and-am-rename.sh    | 1 +\n t/t4153-am-resume-override-opts.sh | 1 +\n t/t7201-co.sh                      | 1 +\n 4 files changed, 10 insertions(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8ff29ed09e..8c8e8b4868 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -239,7 +239,8 @@ enum rename_type {\n struct stage_data {\n \tstruct diff_filespec stages[4]; /* mostly for oid & mode; maybe path */\n \tstruct rename_conflict_info *rename_conflict_info;\n-\tunsigned processed:1;\n+\tunsigned processed:1,\n+\t\t rename_conflict_info_owned:1;\n };\n \n struct rename {\n@@ -308,6 +309,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \n \tci->ren1->dst_entry->processed = 0;\n \tci->ren1->dst_entry->rename_conflict_info = ci;\n+\tci->ren1->dst_entry->rename_conflict_info_owned = 1;\n \tif (ren2) {\n \t\tci->ren2->dst_entry->rename_conflict_info = ci;\n \t}\n@@ -3055,6 +3057,10 @@ static void final_cleanup_rename(struct string_list *rename)\n \tfor (i = 0; i < rename->nr; i++) {\n \t\tre = rename->items[i].util;\n \t\tdiff_free_filepair(re->pair);\n+\t\tif (re->src_entry->rename_conflict_info_owned)\n+\t\t\tFREE_AND_NULL(re->src_entry->rename_conflict_info);\n+\t\tif (re->dst_entry->rename_conflict_info_owned)\n+\t\t\tFREE_AND_NULL(re->dst_entry->rename_conflict_info);\n \t}\n \tstring_list_clear(rename, 1);\n \tfree(rename);\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex f18bae9450..328c1d3a3f 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -2,6 +2,7 @@\n \n test_description='git rebase + directory rename tests'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-rebase.sh\n \ndiff --git a/t/t4153-am-resume-override-opts.sh b/t/t4153-am-resume-override-opts.sh\nindex 4add7c7757..6bc377b917 100755\n--- a/t/t4153-am-resume-override-opts.sh\n+++ b/t/t4153-am-resume-override-opts.sh\n@@ -2,6 +2,7 @@\n \n test_description='git-am command-line options override saved options'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n \ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 42352dc0db..189d8e341b 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -23,6 +23,7 @@ Test switching across them.\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 test_tick\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496124","messageId":"8c55370b8efe0d83946af5ad007c60c902974c72.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 07/29] revision: fix leaking display notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:46:57Z","receivedAt":"2024-06-03T09:47:01Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free the display notes options embedded into `struct revision`.\nImplement a new function `release_display_notes()` that we can call in\n`release_revisions()` to fix this.\n\nThere is another gotcha here though: we play some games with the string\nlist used to track extra notes refs, where we sometimes set the bit that\nindicates that strings should be strdup'd and sometimes unset it. This\ndance is done to avoid a copy of an already-allocated string when we\ncall `enable_ref_display_notes()`. But this dance is rather pointless as\nwe can instead call `string_list_append_nodup()` to transfer ownership\nof the allocated string to the list.\n\nRefactor the code to do so and drop the `strdup_strings` dance.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n notes.c          | 14 ++++++++------\n notes.h          |  5 +++++\n revision.c       |  1 +\n t/t3301-notes.sh |  1 +\n 4 files changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 53ca25c814..6a157e34ce 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1060,6 +1060,12 @@ void init_display_notes(struct display_notes_opt *opt)\n {\n \tmemset(opt, 0, sizeof(*opt));\n \topt->use_default_notes = -1;\n+\tstring_list_init_dup(&opt->extra_notes_refs);\n+}\n+\n+void release_display_notes(struct display_notes_opt *opt)\n+{\n+\tstring_list_clear(&opt->extra_notes_refs, 0);\n }\n \n void enable_default_display_notes(struct display_notes_opt *opt, int *show_notes)\n@@ -1073,19 +1079,15 @@ void enable_ref_display_notes(struct display_notes_opt *opt, int *show_notes,\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addstr(&buf, ref);\n \texpand_notes_ref(&buf);\n-\tstring_list_append(&opt->extra_notes_refs,\n-\t\t\tstrbuf_detach(&buf, NULL));\n+\tstring_list_append_nodup(&opt->extra_notes_refs,\n+\t\t\t\t strbuf_detach(&buf, NULL));\n \t*show_notes = 1;\n }\n \n void disable_display_notes(struct display_notes_opt *opt, int *show_notes)\n {\n \topt->use_default_notes = -1;\n-\t/* we have been strdup'ing ourselves, so trick\n-\t * string_list into free()ing strings */\n-\topt->extra_notes_refs.strdup_strings = 1;\n \tstring_list_clear(&opt->extra_notes_refs, 0);\n-\topt->extra_notes_refs.strdup_strings = 0;\n \t*show_notes = 0;\n }\n \ndiff --git a/notes.h b/notes.h\nindex 064fd7143a..235216944b 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -275,6 +275,11 @@ struct display_notes_opt {\n  */\n void init_display_notes(struct display_notes_opt *opt);\n \n+/*\n+ * Release resources acquired by the display_notes_opt.\n+ */\n+void release_display_notes(struct display_notes_opt *opt);\n+\n /*\n  * This family of functions enables or disables the display of notes. In\n  * particular, 'enable_default_display_notes' will display the default notes,\ndiff --git a/revision.c b/revision.c\nindex af95502d92..75e71bcaea 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3168,6 +3168,7 @@ void release_revisions(struct rev_info *revs)\n {\n \tfree_commit_list(revs->commits);\n \tfree_commit_list(revs->ancestry_path_bottoms);\n+\trelease_display_notes(&revs->notes_opt);\n \tobject_array_clear(&revs->pending);\n \tobject_array_clear(&revs->boundary_commits);\n \trelease_revisions_cmdline(&revs->cmdline);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex cf23c06c09..536bd11ff4 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test commit notes'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n write_script fake_editor <<\\EOF\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496125","messageId":"17671dc9cfbf51e091575241565fb1460a498a7b.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 08/29] notes: fix memory leak when pruning notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:02Z","receivedAt":"2024-06-03T09:47:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `prune_notes()` we first store the notes that are to be deleted in a\nlocal list, and then iterate through that list to delete those notes one\nby one. We never free the list though and thus leak its memory. Fix\nthis.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n notes.c                | 7 ++++++-\n t/t3306-notes-prune.sh | 1 +\n 2 files changed, 7 insertions(+), 1 deletion(-)\n\ndiff --git a/notes.c b/notes.c\nindex 6a157e34ce..244b5c4b1c 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1219,11 +1219,16 @@ void prune_notes(struct notes_tree *t, int flags)\n \tfor_each_note(t, 0, prune_notes_helper, &l);\n \n \twhile (l) {\n+\t\tstruct note_delete_list *next;\n+\n \t\tif (flags & NOTES_PRUNE_VERBOSE)\n \t\t\tprintf(\"%s\\n\", hash_to_hex(l->sha1));\n \t\tif (!(flags & NOTES_PRUNE_DRYRUN))\n \t\t\tremove_note(t, l->sha1);\n-\t\tl = l->next;\n+\n+\t\tnext = l->next;\n+\t\tfree(l);\n+\t\tl = next;\n \t}\n }\n \ndiff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh\nindex 8f4102ff9e..b6e9f643e3 100755\n--- a/t/t3306-notes-prune.sh\n+++ b/t/t3306-notes-prune.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test git notes prune'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup: create a few commits with notes' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496126","messageId":"5e01c39e357bd3f06c1312c27b1a59cd9f741fda.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 09/29] builtin/rev-list: fix leaking bitmap index when calculating disk usage","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:06Z","receivedAt":"2024-06-03T09:47:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"git-rev-list(1) can speed up its object size calculations for reachable\nobjects via a bitmap walk, if there is any bitmap. This is done in\n`try_bitmap_disk_usage()`, which tries to optimistically load the bitmap\nand then use it, if available. It never frees it though, leading to a\nmemory leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rev-list.c     | 2 ++\n t/t6115-rev-list-du.sh | 2 ++\n 2 files changed, 4 insertions(+)\n\ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 77803727e0..97d077a994 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -508,6 +508,8 @@ static int try_bitmap_disk_usage(struct rev_info *revs,\n \n \tsize_from_bitmap = get_disk_usage_from_bitmap(bitmap_git, revs);\n \tprint_disk_usage(size_from_bitmap);\n+\n+\tfree_bitmap_index(bitmap_git);\n \treturn 0;\n }\n \ndiff --git a/t/t6115-rev-list-du.sh b/t/t6115-rev-list-du.sh\nindex c0cfda62fa..21c4a211b1 100755\n--- a/t/t6115-rev-list-du.sh\n+++ b/t/t6115-rev-list-du.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='basic tests of rev-list --disk-usage'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # we want a mix of reachable and unreachable, as well as\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496127","messageId":"2ac24bf942fcc78fbbeedc05d663b5011dfb7ff1.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 10/29] object-name: free leaking object contexts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:11Z","receivedAt":"2024-06-03T09:47:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While it is documented in `struct object_context::path` that this\nvariable needs to be released by the caller, this fact is rather easy to\nmiss given that we do not ever provide a function to release the object\ncontext. And of course, while some callers dutifully release the path,\nmany others don't.\n\nIntroduce a new `object_context_release()` function that releases the\npath. Convert callsites that used to free the path to use that new\nfunction and add missing calls to callsites that were leaking memory.\nRefactor those callsites as required to have a single return path, only.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c               | 17 ++++++----\n builtin/grep.c                   |  4 +--\n builtin/log.c                    |  6 ++--\n builtin/ls-tree.c                |  3 +-\n builtin/rev-parse.c              |  2 ++\n builtin/stash.c                  | 12 +++++--\n list-objects-filter.c            |  2 ++\n object-name.c                    | 40 ++++++++++++++++-------\n object-name.h                    |  2 ++\n revision.c                       | 55 ++++++++++++++++++++------------\n t/t7012-skip-worktree-writing.sh |  1 +\n 11 files changed, 97 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 43a1d7ac49..18fe58d6b8 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -102,7 +102,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,\n \tenum object_type type;\n \tchar *buf;\n \tunsigned long size;\n-\tstruct object_context obj_context;\n+\tstruct object_context obj_context = {0};\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \tstruct strbuf sb = STRBUF_INIT;\n \tunsigned flags = OBJECT_INFO_LOOKUP_REPLACE;\n@@ -163,7 +163,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,\n \t\tgoto cleanup;\n \n \tcase 'e':\n-\t\treturn !repo_has_object_file(the_repository, &oid);\n+\t\tret = !repo_has_object_file(the_repository, &oid);\n+\t\tgoto cleanup;\n \n \tcase 'w':\n \n@@ -268,7 +269,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,\n \tret = 0;\n cleanup:\n \tfree(buf);\n-\tfree(obj_context.path);\n+\tobject_context_release(&obj_context);\n \treturn ret;\n }\n \n@@ -520,7 +521,7 @@ static void batch_one_object(const char *obj_name,\n \t\t\t     struct batch_options *opt,\n \t\t\t     struct expand_data *data)\n {\n-\tstruct object_context ctx;\n+\tstruct object_context ctx = {0};\n \tint flags =\n \t\tGET_OID_HASH_ANY |\n \t\t(opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n@@ -557,7 +558,8 @@ static void batch_one_object(const char *obj_name,\n \t\t\tbreak;\n \t\t}\n \t\tfflush(stdout);\n-\t\treturn;\n+\n+\t\tgoto out;\n \t}\n \n \tif (ctx.mode == 0) {\n@@ -565,10 +567,13 @@ static void batch_one_object(const char *obj_name,\n \t\t       (uintmax_t)ctx.symlink_path.len,\n \t\t       opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n \t\tfflush(stdout);\n-\t\treturn;\n+\t\tgoto out;\n \t}\n \n \tbatch_object_write(obj_name, scratch, opt, data, NULL, 0);\n+\n+out:\n+\tobject_context_release(&ctx);\n }\n \n struct object_cb_data {\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 5777ba82a9..dfc3c3e8bd 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -1114,7 +1114,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tfor (i = 0; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tstruct object_id oid;\n-\t\tstruct object_context oc;\n+\t\tstruct object_context oc = {0};\n \t\tstruct object *object;\n \n \t\tif (!strcmp(arg, \"--\")) {\n@@ -1140,7 +1140,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tif (!seen_dashdash)\n \t\t\tverify_non_filename(prefix, arg);\n \t\tadd_object_array_with_path(object, arg, &list, oc.mode, oc.path);\n-\t\tfree(oc.path);\n+\t\tobject_context_release(&oc);\n \t}\n \n \t/*\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 4e4b645a21..37ecb3ff8b 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -682,7 +682,7 @@ static void show_tagger(const char *buf, struct rev_info *rev)\n static int show_blob_object(const struct object_id *oid, struct rev_info *rev, const char *obj_name)\n {\n \tstruct object_id oidc;\n-\tstruct object_context obj_context;\n+\tstruct object_context obj_context = {0};\n \tchar *buf;\n \tunsigned long size;\n \n@@ -698,7 +698,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \tif (!obj_context.path ||\n \t    !textconv_object(the_repository, obj_context.path,\n \t\t\t     obj_context.mode, &oidc, 1, &buf, &size)) {\n-\t\tfree(obj_context.path);\n+\t\tobject_context_release(&obj_context);\n \t\treturn stream_blob_to_fd(1, oid, NULL, 0);\n \t}\n \n@@ -706,7 +706,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \t\tdie(_(\"git show %s: bad file\"), obj_name);\n \n \twrite_or_die(1, buf, size);\n-\tfree(obj_context.path);\n+\tobject_context_release(&obj_context);\n \treturn 0;\n }\n \ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 7bf84b235c..bf372c67d7 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -367,7 +367,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \tstruct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format;\n-\tstruct object_context obj_context;\n+\tstruct object_context obj_context = {0};\n \tint ret;\n \n \tgit_config(git_default_config, NULL);\n@@ -441,5 +441,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \n \tret = !!read_tree(the_repository, tree, &options.pathspec, fn, &options);\n \tclear_pathspec(&options.pathspec);\n+\tobject_context_release(&obj_context);\n \treturn ret;\n }\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex ab8a8f3b0e..2e64f5bda7 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -1128,6 +1128,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t}\n \t\tif (!get_oid_with_context(the_repository, name,\n \t\t\t\t\t  flags, &oid, &unused)) {\n+\t\t\tobject_context_release(&unused);\n \t\t\tif (output_algo)\n \t\t\t\trepo_oid_to_algop(the_repository, &oid,\n \t\t\t\t\t\t  output_algo, &oid);\n@@ -1137,6 +1138,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tshow_rev(type, &oid, name);\n \t\t\tcontinue;\n \t\t}\n+\t\tobject_context_release(&unused);\n \t\tif (verify)\n \t\t\tdie_no_single_rev(quiet);\n \t\tif (has_dashdash)\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7859bc0866..628d848a0b 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1018,13 +1018,14 @@ static int store_stash(int argc, const char **argv, const char *prefix)\n \tint quiet = 0;\n \tconst char *stash_msg = NULL;\n \tstruct object_id obj;\n-\tstruct object_context dummy;\n+\tstruct object_context dummy = {0};\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet\")),\n \t\tOPT_STRING('m', \"message\", &stash_msg, \"message\",\n \t\t\t   N_(\"stash message\")),\n \t\tOPT_END()\n \t};\n+\tint ret;\n \n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     git_stash_store_usage,\n@@ -1043,10 +1044,15 @@ static int store_stash(int argc, const char **argv, const char *prefix)\n \t\tif (!quiet)\n \t\t\tfprintf_ln(stderr, _(\"Cannot update %s with %s\"),\n \t\t\t\t\t     ref_stash, argv[0]);\n-\t\treturn -1;\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n \n-\treturn do_store_stash(&obj, stash_msg, quiet);\n+\tret = do_store_stash(&obj, stash_msg, quiet);\n+\n+out:\n+\tobject_context_release(&dummy);\n+\treturn ret;\n }\n \n static void add_pathspecs(struct strvec *args,\ndiff --git a/list-objects-filter.c b/list-objects-filter.c\nindex 4346f8da45..c95ec3509a 100644\n--- a/list-objects-filter.c\n+++ b/list-objects-filter.c\n@@ -542,6 +542,8 @@ static void filter_sparse_oid__init(\n \tfilter->filter_data = d;\n \tfilter->filter_object_fn = filter_sparse;\n \tfilter->free_fn = filter_sparse_free;\n+\n+\tobject_context_release(&oc);\n }\n \n /*\ndiff --git a/object-name.c b/object-name.c\nindex 523af6f64f..0471fafc98 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1757,6 +1757,11 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n \treturn check_refname_format(sb->buf, 0);\n }\n \n+void object_context_release(struct object_context *ctx)\n+{\n+\tfree(ctx->path);\n+}\n+\n /*\n  * This is like \"get_oid_basic()\", except it allows \"object ID expressions\",\n  * notably \"xyz^\" for \"parent of xyz\"\n@@ -1764,7 +1769,9 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n int repo_get_oid(struct repository *r, const char *name, struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, 0, oid, &unused);\n+\tint ret = get_oid_with_context(r, name, 0, oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n /*\n@@ -1802,8 +1809,10 @@ int repo_get_oid_committish(struct repository *r,\n \t\t\t    struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_COMMITTISH,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_COMMITTISH,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_treeish(struct repository *r,\n@@ -1811,8 +1820,10 @@ int repo_get_oid_treeish(struct repository *r,\n \t\t\t struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_TREEISH,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_TREEISH,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_commit(struct repository *r,\n@@ -1820,8 +1831,10 @@ int repo_get_oid_commit(struct repository *r,\n \t\t\tstruct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_COMMIT,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_COMMIT,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_tree(struct repository *r,\n@@ -1829,8 +1842,10 @@ int repo_get_oid_tree(struct repository *r,\n \t\t      struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_TREE,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_TREE,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_blob(struct repository *r,\n@@ -1838,8 +1853,10 @@ int repo_get_oid_blob(struct repository *r,\n \t\t      struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_BLOB,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_BLOB,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n /* Must be called only when object_name:filename doesn't exist. */\n@@ -2117,6 +2134,7 @@ void maybe_die_on_misspelt_object_name(struct repository *r,\n \tstruct object_id oid;\n \tget_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY,\n \t\t\t       prefix, &oid, &oc);\n+\tobject_context_release(&oc);\n }\n \n enum get_oid_result get_oid_with_context(struct repository *repo,\ndiff --git a/object-name.h b/object-name.h\nindex 064ddc97d1..8dba4a47a4 100644\n--- a/object-name.h\n+++ b/object-name.h\n@@ -22,6 +22,8 @@ struct object_context {\n \tchar *path;\n };\n \n+void object_context_release(struct object_context *ctx);\n+\n /*\n  * Return an abbreviated sha1 unique within this repository's object database.\n  * The result will be at least `len` characters long, and will be NUL\ndiff --git a/revision.c b/revision.c\nindex 75e71bcaea..82c0aadb42 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2130,30 +2130,26 @@ static int handle_dotdot(const char *arg,\n \t\t\t struct rev_info *revs, int flags,\n \t\t\t int cant_be_filename)\n {\n-\tstruct object_context a_oc, b_oc;\n+\tstruct object_context a_oc = {0}, b_oc = {0};\n \tchar *dotdot = strstr(arg, \"..\");\n \tint ret;\n \n \tif (!dotdot)\n \t\treturn -1;\n \n-\tmemset(&a_oc, 0, sizeof(a_oc));\n-\tmemset(&b_oc, 0, sizeof(b_oc));\n-\n \t*dotdot = '\\0';\n \tret = handle_dotdot_1(arg, dotdot, revs, flags, cant_be_filename,\n \t\t\t      &a_oc, &b_oc);\n \t*dotdot = '.';\n \n-\tfree(a_oc.path);\n-\tfree(b_oc.path);\n-\n+\tobject_context_release(&a_oc);\n+\tobject_context_release(&b_oc);\n \treturn ret;\n }\n \n static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)\n {\n-\tstruct object_context oc;\n+\tstruct object_context oc = {0};\n \tchar *mark;\n \tstruct object *object;\n \tstruct object_id oid;\n@@ -2161,6 +2157,7 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \tconst char *arg = arg_;\n \tint cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;\n \tunsigned get_sha1_flags = GET_OID_RECORD_PATH;\n+\tint ret;\n \n \tflags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;\n \n@@ -2169,17 +2166,22 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \t\t * Just \"..\"?  That is not a range but the\n \t\t * pathspec for the parent directory.\n \t\t */\n-\t\treturn -1;\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n \n-\tif (!handle_dotdot(arg, revs, flags, revarg_opt))\n-\t\treturn 0;\n+\tif (!handle_dotdot(arg, revs, flags, revarg_opt)) {\n+\t\tret = 0;\n+\t\tgoto out;\n+\t}\n \n \tmark = strstr(arg, \"^@\");\n \tif (mark && !mark[2]) {\n \t\t*mark = 0;\n-\t\tif (add_parents_only(revs, arg, flags, 0))\n-\t\t\treturn 0;\n+\t\tif (add_parents_only(revs, arg, flags, 0)) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t\t*mark = '^';\n \t}\n \tmark = strstr(arg, \"^!\");\n@@ -2194,8 +2196,10 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \n \t\tif (mark[2]) {\n \t\t\tif (strtol_i(mark + 2, 10, &exclude_parent) ||\n-\t\t\t    exclude_parent < 1)\n-\t\t\t\treturn -1;\n+\t\t\t    exclude_parent < 1) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t*mark = 0;\n@@ -2217,17 +2221,25 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \t * should error out if we can't even get an oid, as\n \t * `--missing=print` should be able to report missing oids.\n \t */\n-\tif (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc))\n-\t\treturn revs->ignore_missing ? 0 : -1;\n+\tif (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc)) {\n+\t\tret = revs->ignore_missing ? 0 : -1;\n+\t\tgoto out;\n+\t}\n \tif (!cant_be_filename)\n \t\tverify_non_filename(revs->prefix, arg);\n \tobject = get_reference(revs, arg, &oid, flags ^ local_flags);\n-\tif (!object)\n-\t\treturn (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1;\n+\tif (!object) {\n+\t\tret = (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1;\n+\t\tgoto out;\n+\t}\n \tadd_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);\n \tadd_pending_object_with_path(revs, object, arg, oc.mode, oc.path);\n-\tfree(oc.path);\n-\treturn 0;\n+\n+\tret = 0;\n+\n+out:\n+\tobject_context_release(&oc);\n+\treturn ret;\n }\n \n int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt)\n@@ -3062,6 +3074,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\t\tdiagnose_missing_default(revs->def);\n \t\tobject = get_reference(revs, revs->def, &oid, 0);\n \t\tadd_pending_object_with_mode(revs, object, revs->def, oc.mode);\n+\t\tobject_context_release(&oc);\n \t}\n \n \t/* Did the user ask for any diff output? Run the diff! */\ndiff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh\nindex cd5c20fe51..d984200c17 100755\n--- a/t/t7012-skip-worktree-writing.sh\n+++ b/t/t7012-skip-worktree-writing.sh\n@@ -5,6 +5,7 @@\n \n test_description='test worktree writing operations when skip-worktree is used'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496128","messageId":"8349f6acbd9309f8b50a07c1583b4946d25fdc64.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 11/29] builtin/difftool: plug memory leaks in `run_dir_diff()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:16Z","receivedAt":"2024-06-03T09:47:20Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're leaking a bunch of memory leaks in `run_dir_diff()`. Plug them.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/difftool.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex a130faae4f..63308b1ca7 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -662,6 +662,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \n \tfree(lbase_dir);\n \tfree(rbase_dir);\n+\tstrbuf_release(&info);\n+\tstrbuf_release(&lpath);\n+\tstrbuf_release(&rpath);\n \tstrbuf_release(&ldir);\n \tstrbuf_release(&rdir);\n \tstrbuf_release(&wtdir);\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496129","messageId":"5d9fb78e6394af7956b69ba46ea4975a99958900.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 12/29] builtin/merge-recursive: fix leaking object ID bases","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:21Z","receivedAt":"2024-06-03T09:47:25Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `cmd_merge_recursive()` we have a static array of object ID bases\nthat we pass to `merge_recursive_generic()`. This interface is somewhat\nweird though because the latter function accepts a pointer to a pointer\nof object IDs, which requires us to allocate the object IDs on the heap.\nAnd as we never free those object IDs, the end result is a leak.\n\nWhile we can easily solve this leak by just freeing the respective\nobject IDs, the whole calling convention is somewhat weird. Instead,\nrefactor `merge_recursive_generic()` to accept a plain pointer to object\nIDs so that we can avoid allocating them altogether.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/am.c                              | 6 +++---\n builtin/merge-recursive.c                 | 6 ++----\n merge-recursive.c                         | 8 ++++----\n merge-recursive.h                         | 2 +-\n t/t6432-merge-recursive-space-options.sh  | 1 +\n t/t6434-merge-recursive-rename-options.sh | 1 +\n 6 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 36839029d2..4ba44e2d70 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1573,8 +1573,8 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f\n  */\n static int fall_back_threeway(const struct am_state *state, const char *index_path)\n {\n-\tstruct object_id orig_tree, their_tree, our_tree;\n-\tconst struct object_id *bases[1] = { &orig_tree };\n+\tstruct object_id their_tree, our_tree;\n+\tstruct object_id bases[1] = { 0 };\n \tstruct merge_options o;\n \tstruct commit *result;\n \tchar *their_tree_name;\n@@ -1588,7 +1588,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \tdiscard_index(the_repository->index);\n \tread_index_from(the_repository->index, index_path, get_git_dir());\n \n-\tif (write_index_as_tree(&orig_tree, the_repository->index, index_path, 0, NULL))\n+\tif (write_index_as_tree(&bases[0], the_repository->index, index_path, 0, NULL))\n \t\treturn error(_(\"Repository lacks necessary blobs to fall back on 3-way merge.\"));\n \n \tsay(state, stdout, _(\"Using index info to reconstruct a base tree...\"));\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex c2ce044a20..82bebea15b 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -23,7 +23,7 @@ static char *better_branch_name(const char *branch)\n \n int cmd_merge_recursive(int argc, const char **argv, const char *prefix UNUSED)\n {\n-\tconst struct object_id *bases[21];\n+\tstruct object_id bases[21];\n \tunsigned bases_count = 0;\n \tint i, failed;\n \tstruct object_id h1, h2;\n@@ -49,10 +49,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix UNUSED)\n \t\t\tcontinue;\n \t\t}\n \t\tif (bases_count < ARRAY_SIZE(bases)-1) {\n-\t\t\tstruct object_id *oid = xmalloc(sizeof(struct object_id));\n-\t\t\tif (repo_get_oid(the_repository, argv[i], oid))\n+\t\t\tif (repo_get_oid(the_repository, argv[i], &bases[bases_count++]))\n \t\t\t\tdie(_(\"could not parse object '%s'\"), argv[i]);\n-\t\t\tbases[bases_count++] = oid;\n \t\t}\n \t\telse\n \t\t\twarning(Q_(\"cannot handle more than %d base. \"\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8c8e8b4868..eff73dac02 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3866,7 +3866,7 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n-\t\t\t    const struct object_id **merge_bases,\n+\t\t\t    const struct object_id *merge_bases,\n \t\t\t    struct commit **result)\n {\n \tint clean;\n@@ -3879,10 +3879,10 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\tint i;\n \t\tfor (i = 0; i < num_merge_bases; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, merge_bases[i],\n-\t\t\t\t\t     oid_to_hex(merge_bases[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, &merge_bases[i],\n+\t\t\t\t\t     oid_to_hex(&merge_bases[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n-\t\t\t\t\t   oid_to_hex(merge_bases[i]));\n+\t\t\t\t\t   oid_to_hex(&merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t\tif (num_merge_bases == 1)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex e67d38c303..839eb6436e 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -123,7 +123,7 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n-\t\t\t    const struct object_id **merge_bases,\n+\t\t\t    const struct object_id *merge_bases,\n \t\t\t    struct commit **result);\n \n #endif\ndiff --git a/t/t6432-merge-recursive-space-options.sh b/t/t6432-merge-recursive-space-options.sh\nindex db4b77e63d..c93538b0c3 100755\n--- a/t/t6432-merge-recursive-space-options.sh\n+++ b/t/t6432-merge-recursive-space-options.sh\n@@ -14,6 +14,7 @@ test_description='merge-recursive space options\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 test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b\ndiff --git a/t/t6434-merge-recursive-rename-options.sh b/t/t6434-merge-recursive-rename-options.sh\nindex a11707835b..df1d0c156c 100755\n--- a/t/t6434-merge-recursive-rename-options.sh\n+++ b/t/t6434-merge-recursive-rename-options.sh\n@@ -29,6 +29,7 @@ mentions this in a different context).\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 get_expected_stages () {\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496130","messageId":"930de119116355f0b6df9e7e1d5d0fe38d93755a.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 13/29] merge-recursive: fix memory leak when finalizing merge","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:26Z","receivedAt":"2024-06-03T09:47:30Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We do not free some members of `struct merge_options`' private data.\nFix this to plug those leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-recursive.c               | 3 +++\n t/t1004-read-tree-m-u-wf.sh     | 1 +\n t/t1015-read-index-unmerged.sh  | 2 ++\n t/t3509-cherry-pick-merge-df.sh | 1 +\n 4 files changed, 7 insertions(+)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex eff73dac02..832c8ef3f3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3800,6 +3800,9 @@ static void merge_finalize(struct merge_options *opt)\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opt->priv->needed_rename_limit, 0);\n+\thashmap_clear_and_free(&opt->priv->current_file_dir_set,\n+\t\t\t       struct path_hashmap_entry, e);\n+\tstring_list_clear(&opt->priv->df_conflict_file_set, 0);\n \tFREE_AND_NULL(opt->priv);\n }\n \ndiff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh\nindex 11bf10424f..2b9720b0fe 100755\n--- a/t/t1004-read-tree-m-u-wf.sh\n+++ b/t/t1004-read-tree-m-u-wf.sh\n@@ -5,6 +5,7 @@ test_description='read-tree -m -u checks working tree files'\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 . \"$TEST_DIRECTORY\"/lib-read-tree.sh\n \ndiff --git a/t/t1015-read-index-unmerged.sh b/t/t1015-read-index-unmerged.sh\nindex 55d22da32c..da737a32a2 100755\n--- a/t/t1015-read-index-unmerged.sh\n+++ b/t/t1015-read-index-unmerged.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='Test various callers of read_index_unmerged'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup modify/delete + directory/file conflict' '\ndiff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh\nindex f4159246e1..171cc6d76b 100755\n--- a/t/t3509-cherry-pick-merge-df.sh\n+++ b/t/t3509-cherry-pick-merge-df.sh\n@@ -4,6 +4,7 @@ test_description='Test cherry-pick with directory/file conflicts'\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 test_expect_success 'Initialize repository' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496131","messageId":"3e3243f884abba99ea6143e92689c917937a5cbc.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 14/29] builtin/log: fix leaking commit list in git-cherry(1)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:30Z","receivedAt":"2024-06-03T09:47:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're storing the list of commits that git-cherry(1) is about to print\ninto a temporary list. This list is never getting free'd and thus leaks.\nFix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c     | 6 +++---\n t/t3500-cherry.sh | 1 +\n 2 files changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 37ecb3ff8b..b36fa9d155 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -2675,16 +2675,16 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)\n \t\tcommit_list_insert(commit, &list);\n \t}\n \n-\twhile (list) {\n+\tfor (struct commit_list *l = list; l; l = l->next) {\n \t\tchar sign = '+';\n \n-\t\tcommit = list->item;\n+\t\tcommit = l->item;\n \t\tif (has_commit_patch_id(commit, &ids))\n \t\t\tsign = '-';\n \t\tprint_commit(sign, commit, verbose, abbrev, revs.diffopt.file);\n-\t\tlist = list->next;\n \t}\n \n+\tfree_commit_list(list);\n \tfree_patch_ids(&ids);\n \treturn 0;\n }\ndiff --git a/t/t3500-cherry.sh b/t/t3500-cherry.sh\nindex 78c3eac54b..61ca87512d 100755\n--- a/t/t3500-cherry.sh\n+++ b/t/t3500-cherry.sh\n@@ -11,6 +11,7 @@ checks that git cherry only returns the second patch in the local branch\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 GIT_AUTHOR_EMAIL=bogus_email_address\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496132","messageId":"a754800fd1b7bd753b0d6fdfdd5f7a5cf17ceb98.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 15/29] revision: free diff options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:36Z","receivedAt":"2024-06-03T09:47:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There is a todo comment in `release_revisions()` that mentions that we\nneed to free the diff options, which was added via 54c8a7c379 (revisions\nAPI: add a TODO for diff_free(&revs->diffopt), 2022-04-14). Releasing\nthe diff options wasn't quite feasible at that time because some call\nsites rely on its contents to remain even after the revisions have been\nreleased.\n\nIn fact, there really only are a couple of callsites that misbehave\nhere:\n\n  - `cmd_shortlog()` releases the revisions, but continues to access its\n    file pointer.\n\n  - `do_diff_cache()` creates a shallow copy of `struct diff_options`,\n    but does not set the `no_free` member. Consequently, we end up\n    releasing resources of the caller-provided diff options.\n\n  - `diff_free()` and friends do not play nice when being called\n    multiple times as they don't unset data structures that they have\n    just released.\n\nFix all of those cases and enable the call to `diff_free()`, which plugs\na bunch of memory leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/shortlog.c                        | 5 +----\n diff-lib.c                                | 2 ++\n diff.c                                    | 8 ++++++--\n revision.c                                | 2 +-\n t/t4208-log-magic-pathspec.sh             | 1 +\n t/t6000-rev-list-misc.sh                  | 1 +\n t/t6001-rev-list-graft.sh                 | 1 +\n t/t6013-rev-list-reverse-parents.sh       | 1 +\n t/t6017-rev-list-stdin.sh                 | 1 +\n t/t9500-gitweb-standalone-no-errors.sh    | 1 +\n t/t9502-gitweb-standalone-parse-output.sh | 1 +\n 11 files changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex d4daf31e22..5bde7c68c2 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -460,11 +460,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)\n \telse\n \t\tget_from_rev(&rev, &log);\n \n-\trelease_revisions(&rev);\n-\n \tshortlog_output(&log);\n-\tif (log.file != stdout)\n-\t\tfclose(log.file);\n+\trelease_revisions(&rev);\n \treturn 0;\n }\n \ndiff --git a/diff-lib.c b/diff-lib.c\nindex 5a5a50c5a1..1cbf03bf39 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -662,9 +662,11 @@ int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt)\n \trepo_init_revisions(opt->repo, &revs, NULL);\n \tcopy_pathspec(&revs.prune_data, &opt->pathspec);\n \trevs.diffopt = *opt;\n+\trevs.diffopt.no_free = 1;\n \n \tif (diff_cache(&revs, tree_oid, NULL, 1))\n \t\texit(128);\n+\n \trelease_revisions(&revs);\n \treturn 0;\n }\ndiff --git a/diff.c b/diff.c\nindex e70301df76..53e2f5a42e 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6649,8 +6649,10 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n \n static void diff_free_file(struct diff_options *options)\n {\n-\tif (options->close_file)\n+\tif (options->close_file && options->file) {\n \t\tfclose(options->file);\n+\t\toptions->file = NULL;\n+\t}\n }\n \n static void diff_free_ignore_regex(struct diff_options *options)\n@@ -6661,7 +6663,9 @@ static void diff_free_ignore_regex(struct diff_options *options)\n \t\tregfree(options->ignore_regex[i]);\n \t\tfree(options->ignore_regex[i]);\n \t}\n-\tfree(options->ignore_regex);\n+\n+\tFREE_AND_NULL(options->ignore_regex);\n+\toptions->ignore_regex_nr = 0;\n }\n \n void diff_free(struct diff_options *options)\ndiff --git a/revision.c b/revision.c\nindex 82c0aadb42..99c75c939d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3191,7 +3191,7 @@ void release_revisions(struct rev_info *revs)\n \trelease_revisions_mailmap(revs->mailmap);\n \tfree_grep_patterns(&revs->grep_filter);\n \tgraph_clear(revs->graph);\n-\t/* TODO (need to handle \"no_free\"): diff_free(&revs->diffopt) */\n+\tdiff_free(&revs->diffopt);\n \tdiff_free(&revs->pruning);\n \treflog_walk_info_release(revs->reflog_info);\n \trelease_revisions_topo_walk_info(revs->topo_walk_info);\ndiff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh\nindex 806b2809d4..2a46eb6bed 100755\n--- a/t/t4208-log-magic-pathspec.sh\n+++ b/t/t4208-log-magic-pathspec.sh\n@@ -5,6 +5,7 @@ test_description='magic pathspec tests using git-log'\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 test_expect_success 'setup' '\ndiff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh\nindex 6289a2e8b0..f6d17ee902 100755\n--- a/t/t6000-rev-list-misc.sh\n+++ b/t/t6000-rev-list-misc.sh\n@@ -5,6 +5,7 @@ test_description='miscellaneous rev-list tests'\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 test_expect_success setup '\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 73a2465aa0..3553bbbfe7 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -5,6 +5,7 @@ test_description='Revision traversal vs grafts and path limiter'\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 test_expect_success setup '\ndiff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh\nindex 39793cbbd6..4128269c1d 100755\n--- a/t/t6013-rev-list-reverse-parents.sh\n+++ b/t/t6013-rev-list-reverse-parents.sh\n@@ -5,6 +5,7 @@ test_description='--reverse combines with --parents'\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 \ndiff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh\nindex 4821b90e74..a0a40fe55c 100755\n--- a/t/t6017-rev-list-stdin.sh\n+++ b/t/t6017-rev-list-stdin.sh\n@@ -8,6 +8,7 @@ test_description='log family learns --stdin'\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 () {\ndiff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh\nindex 7679780fb8..ccfa415384 100755\n--- a/t/t9500-gitweb-standalone-no-errors.sh\n+++ b/t/t9500-gitweb-standalone-no-errors.sh\n@@ -13,6 +13,7 @@ or warnings to log.'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./lib-gitweb.sh\n \n # ----------------------------------------------------------------------\ndiff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh\nindex 81d5625557..b41ea19331 100755\n--- a/t/t9502-gitweb-standalone-parse-output.sh\n+++ b/t/t9502-gitweb-standalone-parse-output.sh\n@@ -13,6 +13,7 @@ in the HTTP header or the actual script output.'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./lib-gitweb.sh\n \n # ----------------------------------------------------------------------\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496133","messageId":"8bde010d1d89fce2bbe45e7b136cae0944700419.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 16/29] builtin/stash: fix leak in `show_stash()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:41Z","receivedAt":"2024-06-03T09:47:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We leak the `revision_args()` variable. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/stash.c              | 2 ++\n t/t3420-rebase-autostash.sh  | 1 +\n t/t3907-stash-show-config.sh | 1 +\n 3 files changed, 4 insertions(+)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 628d848a0b..1ed0a9a5d9 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -975,7 +975,9 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tlog_tree_diff_flush(&rev);\n \n \tret = diff_result_code(&rev.diffopt);\n+\n cleanup:\n+\tstrvec_clear(&revision_args);\n \tstrvec_clear(&stash_args);\n \tfree_stash_info(&info);\n \trelease_revisions(&rev);\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 1a820f1481..63e400b89f 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -7,6 +7,7 @@ test_description='git rebase --autostash tests'\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 test_expect_success setup '\ndiff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh\nindex 10914bba7b..7a2eb98b86 100755\n--- a/t/t3907-stash-show-config.sh\n+++ b/t/t3907-stash-show-config.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test git stash show configuration.'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496134","messageId":"cadeb09b9594bffabe9ab183f7efba3f85ebdc59.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 17/29] rerere: fix various trivial leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:46Z","receivedAt":"2024-06-03T09:47:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We leak various different string lists in the rerere code. Free those to\nplug them.\n\nNote that the `merge_rr` variable is intentionally being free'd with the\n`free_util` parameter set to 1. The `util` field is used there to store\nthe IDs of every rerere item and thus needs to be freed, as well.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n rerere.c                      | 3 +++\n t/t1021-rerere-in-workdir.sh  | 1 +\n t/t3504-cherry-pick-rerere.sh | 1 +\n t/t7600-merge.sh              | 1 +\n 4 files changed, 6 insertions(+)\n\ndiff --git a/rerere.c b/rerere.c\nindex c7e1f8fd25..10382da55c 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -849,6 +849,8 @@ static int do_plain_rerere(struct repository *r,\n \tif (update.nr)\n \t\tupdate_paths(r, &update);\n \n+\tstring_list_clear(&conflict, 0);\n+\tstring_list_clear(&update, 0);\n \treturn write_rr(rr, fd);\n }\n \n@@ -912,6 +914,7 @@ int repo_rerere(struct repository *r, int flags)\n \t\treturn 0;\n \tstatus = do_plain_rerere(r, &merge_rr, fd);\n \tfree_rerere_dirs();\n+\tstring_list_clear(&merge_rr, 1);\n \treturn status;\n }\n \ndiff --git a/t/t1021-rerere-in-workdir.sh b/t/t1021-rerere-in-workdir.sh\nindex 0b892894eb..69bf9476cb 100755\n--- a/t/t1021-rerere-in-workdir.sh\n+++ b/t/t1021-rerere-in-workdir.sh\n@@ -4,6 +4,7 @@ test_description='rerere run in a workdir'\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 test_expect_success SYMLINKS setup '\ndiff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh\nindex 4581ae98b8..597c98e9c5 100755\n--- a/t/t3504-cherry-pick-rerere.sh\n+++ b/t/t3504-cherry-pick-rerere.sh\n@@ -5,6 +5,7 @@ test_description='cherry-pick should rerere for conflicts'\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 test_expect_success setup '\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099..041f1077b0 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -29,6 +29,7 @@ Testing basic merge operations/option parsing.\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 . \"$TEST_DIRECTORY\"/lib-gpg.sh\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496135","messageId":"cff6303ce1ce8e921adfae3eada216eca71b2c29.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 18/29] config: fix leaking \"core.notesref\" variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:50Z","receivedAt":"2024-06-03T09:47:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The variable used to track the \"core.notesref\" config is not getting\nfreed before we assign to it and thus leaks. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c                            | 1 +\n t/t3308-notes-merge.sh              | 1 +\n t/t3309-notes-merge-auto-resolve.sh | 1 +\n 3 files changed, 3 insertions(+)\n\ndiff --git a/config.c b/config.c\nindex abce05b774..742175c130 100644\n--- a/config.c\n+++ b/config.c\n@@ -1565,6 +1565,7 @@ static int git_default_core_config(const char *var, const char *value,\n \tif (!strcmp(var, \"core.notesref\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n+\t\tfree(notes_ref_name);\n \t\tnotes_ref_name = xstrdup(value);\n \t\treturn 0;\n \t}\ndiff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh\nindex 202702be1a..e1d05ff6bc 100755\n--- a/t/t3308-notes-merge.sh\n+++ b/t/t3308-notes-merge.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test merging of notes trees'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t3309-notes-merge-auto-resolve.sh b/t/t3309-notes-merge-auto-resolve.sh\nindex 9bd5dbf341..f55277f499 100755\n--- a/t/t3309-notes-merge-auto-resolve.sh\n+++ b/t/t3309-notes-merge-auto-resolve.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging with auto-resolving strategies'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Set up a notes merge scenario with all kinds of potential conflicts\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496136","messageId":"8ada8864d87b6396e63ac2224f0a9cdc73b2e2df.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 19/29] commit: fix leaking parents when calling `commit_tree_extended()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:47:55Z","receivedAt":"2024-06-03T09:47:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When creating commits via `commit_tree_extended()`, the caller passes in\na string list of parents. This call implicitly transfers ownership of\nthat list to the function, which is quite surprising to begin with. But\nto make matters worse, `commit_tree_extended()` doesn't even bother to\nfree the list of parents in error cases. The result is a memory leak,\nand one that the caller cannot fix by themselves because they do not\nknow whether parts of the string list have already been released.\n\nRefactor the code such that callers can keep ownership of the list of\nparents, which is getting indicated by parameter being a constant\npointer now. Free the lists at the calling site and add a common exit\npath to those sites as required.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/am.c                       |  1 +\n builtin/commit-tree.c              | 11 ++++++++---\n builtin/commit.c                   |  3 ++-\n builtin/merge.c                    |  6 +++++-\n builtin/replay.c                   | 14 +++++++++-----\n builtin/stash.c                    |  9 ++++-----\n commit.c                           | 26 ++++++++++++--------------\n commit.h                           | 10 +++++-----\n notes-merge.c                      |  1 +\n notes-utils.c                      |  8 ++++++--\n notes-utils.h                      |  2 +-\n sequencer.c                        |  1 +\n t/t3403-rebase-skip.sh             |  1 +\n t/t3424-rebase-empty.sh            |  1 +\n t/t3505-cherry-pick-empty.sh       |  1 +\n t/t7505-prepare-commit-msg-hook.sh |  1 +\n 16 files changed, 59 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 4ba44e2d70..faccc45b13 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1718,6 +1718,7 @@ static void do_commit(const struct am_state *state)\n \n \trun_hooks(\"post-applypatch\");\n \n+\tfree_commit_list(parents);\n \tstrbuf_release(&sb);\n }\n \ndiff --git a/builtin/commit-tree.c b/builtin/commit-tree.c\nindex 1bb7819839..84bb450222 100644\n--- a/builtin/commit-tree.c\n+++ b/builtin/commit-tree.c\n@@ -111,6 +111,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"GPG sign commit\"), PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \t\tOPT_END()\n \t};\n+\tint ret;\n \n \tgit_config(git_default_config, NULL);\n \n@@ -132,11 +133,15 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \n \tif (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid,\n \t\t\tNULL, sign_commit)) {\n-\t\tstrbuf_release(&buffer);\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tprintf(\"%s\\n\", oid_to_hex(&commit_oid));\n+\tret = 0;\n+\n+out:\n+\tfree_commit_list(parents);\n \tstrbuf_release(&buffer);\n-\treturn 0;\n+\treturn ret;\n }\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex dcaf4efa03..d5713455e5 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1848,7 +1848,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n \t}\n-\tfree_commit_extra_headers(extra);\n \n \tif (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,\n \t\t\t\t    &err)) {\n@@ -1890,6 +1889,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n \n cleanup:\n+\tfree_commit_extra_headers(extra);\n+\tfree_commit_list(parents);\n \tstrbuf_release(&author_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..50b0c87a95 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -895,7 +895,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n {\n \tstruct object_id result_tree, result_commit;\n-\tstruct commit_list *parents, **pptr = &parents;\n+\tstruct commit_list *parents = NULL, **pptr = &parents;\n \n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n@@ -911,7 +911,9 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n \t\t\t&result_commit, NULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \tfinish(head, remoteheads, &result_commit, \"In-index merge\");\n+\n \tremove_merge_branch_state(the_repository);\n+\tfree_commit_list(parents);\n \treturn 0;\n }\n \n@@ -937,8 +939,10 @@ static int finish_automerge(struct commit *head,\n \t\tdie(_(\"failed to write commit object\"));\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(head, remoteheads, &result_commit, buf.buf);\n+\n \tstrbuf_release(&buf);\n \tremove_merge_branch_state(the_repository);\n+\tfree_commit_list(parents);\n \treturn 0;\n }\n \ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 6bf0691f15..0448326636 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -52,11 +52,11 @@ static struct commit *create_commit(struct tree *tree,\n \t\t\t\t    struct commit *parent)\n {\n \tstruct object_id ret;\n-\tstruct object *obj;\n+\tstruct object *obj = NULL;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n \tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n-\tstruct commit_extra_header *extra;\n+\tstruct commit_extra_header *extra = NULL;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n \tconst char *message = repo_logmsg_reencode(the_repository, based_on,\n@@ -73,12 +73,16 @@ static struct commit *create_commit(struct tree *tree,\n \tif (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,\n \t\t\t\t &ret, author, NULL, sign_commit, extra)) {\n \t\terror(_(\"failed to write commit object\"));\n-\t\treturn NULL;\n+\t\tgoto out;\n \t}\n-\tfree(author);\n-\tstrbuf_release(&msg);\n \n \tobj = parse_object(the_repository, &ret);\n+\n+out:\n+\tfree_commit_extra_headers(extra);\n+\tfree_commit_list(parents);\n+\tstrbuf_release(&msg);\n+\tfree(author);\n \treturn (struct commit *)obj;\n }\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 1ed0a9a5d9..46b981c4dd 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1416,6 +1416,9 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tgoto done;\n \t}\n \n+\tfree_commit_list(parents);\n+\tparents = NULL;\n+\n \tif (include_untracked) {\n \t\tif (save_untracked_files(info, &msg, untracked_files)) {\n \t\t\tif (!quiet)\n@@ -1461,11 +1464,6 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \telse\n \t\tstrbuf_insertf(stash_msg_buf, 0, \"On %s: \", branch_name);\n \n-\t/*\n-\t * `parents` will be empty after calling `commit_tree()`, so there is\n-\t * no need to call `free_commit_list()`\n-\t */\n-\tparents = NULL;\n \tif (untracked_commit_option)\n \t\tcommit_list_insert(lookup_commit(the_repository,\n \t\t\t\t\t\t &info->u_commit),\n@@ -1487,6 +1485,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \tstrbuf_release(&commit_tree_label);\n \tstrbuf_release(&msg);\n \tstrbuf_release(&untracked_files);\n+\tfree_commit_list(parents);\n \treturn ret;\n }\n \ndiff --git a/commit.c b/commit.c\nindex 1d08951007..f674eca320 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1262,7 +1262,7 @@ int remove_signature(struct strbuf *buf)\n \treturn sigs[0].start != NULL;\n }\n \n-static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)\n+static void handle_signed_tag(const struct commit *parent, struct commit_extra_header ***tail)\n {\n \tstruct merge_remote_desc *desc;\n \tstruct commit_extra_header *mergetag;\n@@ -1359,17 +1359,17 @@ void verify_merge_signature(struct commit *commit, int verbosity,\n \tsignature_check_clear(&signature_check);\n }\n \n-void append_merge_tag_headers(struct commit_list *parents,\n+void append_merge_tag_headers(const struct commit_list *parents,\n \t\t\t      struct commit_extra_header ***tail)\n {\n \twhile (parents) {\n-\t\tstruct commit *parent = parents->item;\n+\t\tconst struct commit *parent = parents->item;\n \t\thandle_signed_tag(parent, tail);\n \t\tparents = parents->next;\n \t}\n }\n \n-static int convert_commit_extra_headers(struct commit_extra_header *orig,\n+static int convert_commit_extra_headers(const struct commit_extra_header *orig,\n \t\t\t\t\tstruct commit_extra_header **result)\n {\n \tconst struct git_hash_algo *compat = the_repository->compat_hash_algo;\n@@ -1403,7 +1403,7 @@ static int convert_commit_extra_headers(struct commit_extra_header *orig,\n }\n \n static void add_extra_header(struct strbuf *buffer,\n-\t\t\t     struct commit_extra_header *extra)\n+\t\t\t     const struct commit_extra_header *extra)\n {\n \tstrbuf_addstr(buffer, extra->key);\n \tif (extra->len)\n@@ -1517,7 +1517,7 @@ void free_commit_extra_headers(struct commit_extra_header *extra)\n }\n \n int commit_tree(const char *msg, size_t msg_len, const struct object_id *tree,\n-\t\tstruct commit_list *parents, struct object_id *ret,\n+\t\tconst struct commit_list *parents, struct object_id *ret,\n \t\tconst char *author, const char *sign_commit)\n {\n \tstruct commit_extra_header *extra = NULL, **tail = &extra;\n@@ -1649,7 +1649,7 @@ static void write_commit_tree(struct strbuf *buffer, const char *msg, size_t msg\n \t\t\t      const struct object_id *tree,\n \t\t\t      const struct object_id *parents, size_t parents_len,\n \t\t\t      const char *author, const char *committer,\n-\t\t\t      struct commit_extra_header *extra)\n+\t\t\t      const struct commit_extra_header *extra)\n {\n \tint encoding_is_utf8;\n \tsize_t i;\n@@ -1690,10 +1690,10 @@ static void write_commit_tree(struct strbuf *buffer, const char *msg, size_t msg\n \n int commit_tree_extended(const char *msg, size_t msg_len,\n \t\t\t const struct object_id *tree,\n-\t\t\t struct commit_list *parents, struct object_id *ret,\n+\t\t\t const struct commit_list *parents, struct object_id *ret,\n \t\t\t const char *author, const char *committer,\n \t\t\t const char *sign_commit,\n-\t\t\t struct commit_extra_header *extra)\n+\t\t\t const struct commit_extra_header *extra)\n {\n \tstruct repository *r = the_repository;\n \tint result = 0;\n@@ -1715,10 +1715,8 @@ int commit_tree_extended(const char *msg, size_t msg_len,\n \tnparents = commit_list_count(parents);\n \tCALLOC_ARRAY(parent_buf, nparents);\n \ti = 0;\n-\twhile (parents) {\n-\t\tstruct commit *parent = pop_commit(&parents);\n-\t\toidcpy(&parent_buf[i++], &parent->object.oid);\n-\t}\n+\tfor (const struct commit_list *p = parents; p; p = p->next)\n+\t\toidcpy(&parent_buf[i++], &p->item->object.oid);\n \n \twrite_commit_tree(&buffer, msg, msg_len, tree, parent_buf, nparents, author, committer, extra);\n \tif (sign_commit && sign_commit_to_strbuf(&sig, &buffer, sign_commit)) {\n@@ -1814,7 +1812,7 @@ int commit_tree_extended(const char *msg, size_t msg_len,\n define_commit_slab(merge_desc_slab, struct merge_remote_desc *);\n static struct merge_desc_slab merge_desc_slab = COMMIT_SLAB_INIT(1, merge_desc_slab);\n \n-struct merge_remote_desc *merge_remote_util(struct commit *commit)\n+struct merge_remote_desc *merge_remote_util(const struct commit *commit)\n {\n \treturn *merge_desc_slab_at(&merge_desc_slab, commit);\n }\ndiff --git a/commit.h b/commit.h\nindex 62fe0d77a7..442e50ff24 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -260,19 +260,19 @@ struct commit_extra_header {\n \tsize_t len;\n };\n \n-void append_merge_tag_headers(struct commit_list *parents,\n+void append_merge_tag_headers(const struct commit_list *parents,\n \t\t\t      struct commit_extra_header ***tail);\n \n int commit_tree(const char *msg, size_t msg_len,\n \t\tconst struct object_id *tree,\n-\t\tstruct commit_list *parents, struct object_id *ret,\n+\t\tconst struct commit_list *parents, struct object_id *ret,\n \t\tconst char *author, const char *sign_commit);\n \n int commit_tree_extended(const char *msg, size_t msg_len,\n \t\t\t const struct object_id *tree,\n-\t\t\t struct commit_list *parents, struct object_id *ret,\n+\t\t\t const struct commit_list *parents, struct object_id *ret,\n \t\t\t const char *author, const char *committer,\n-\t\t\t const char *sign_commit, struct commit_extra_header *);\n+\t\t\t const char *sign_commit, const struct commit_extra_header *);\n \n struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);\n \n@@ -306,7 +306,7 @@ struct merge_remote_desc {\n \tstruct object *obj; /* the named object, could be a tag */\n \tchar name[FLEX_ARRAY];\n };\n-struct merge_remote_desc *merge_remote_util(struct commit *);\n+struct merge_remote_desc *merge_remote_util(const struct commit *);\n void set_merge_remote_desc(struct commit *commit,\n \t\t\t   const char *name, struct object *obj);\n \ndiff --git a/notes-merge.c b/notes-merge.c\nindex 6a9a139b12..f3cc84f45d 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -661,6 +661,7 @@ int notes_merge(struct notes_merge_options *o,\n \t\tcommit_list_insert(local, &parents);\n \t\tcreate_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf,\n \t\t\t\t    o->commit_msg.len, result_oid);\n+\t\tfree_commit_list(parents);\n \t}\n \n found_result:\ndiff --git a/notes-utils.c b/notes-utils.c\nindex 671d1969b1..3198c14e4d 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -9,10 +9,11 @@\n \n void create_notes_commit(struct repository *r,\n \t\t\t struct notes_tree *t,\n-\t\t\t struct commit_list *parents,\n+\t\t\t const struct commit_list *parents,\n \t\t\t const char *msg, size_t msg_len,\n \t\t\t struct object_id *result_oid)\n {\n+\tstruct commit_list *parents_to_free = NULL;\n \tstruct object_id tree_oid;\n \n \tassert(t->initialized);\n@@ -27,7 +28,8 @@ void create_notes_commit(struct repository *r,\n \t\t\tstruct commit *parent = lookup_commit(r, &parent_oid);\n \t\t\tif (repo_parse_commit(r, parent))\n \t\t\t\tdie(\"Failed to find/parse commit %s\", t->ref);\n-\t\t\tcommit_list_insert(parent, &parents);\n+\t\t\tcommit_list_insert(parent, &parents_to_free);\n+\t\t\tparents = parents_to_free;\n \t\t}\n \t\t/* else: t->ref points to nothing, assume root/orphan commit */\n \t}\n@@ -35,6 +37,8 @@ void create_notes_commit(struct repository *r,\n \tif (commit_tree(msg, msg_len, &tree_oid, parents, result_oid, NULL,\n \t\t\tNULL))\n \t\tdie(\"Failed to commit notes tree to database\");\n+\n+\tfree_commit_list(parents_to_free);\n }\n \n void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)\ndiff --git a/notes-utils.h b/notes-utils.h\nindex d9b3c09eaf..c54b1fe141 100644\n--- a/notes-utils.h\n+++ b/notes-utils.h\n@@ -20,7 +20,7 @@ struct repository;\n  */\n void create_notes_commit(struct repository *r,\n \t\t\t struct notes_tree *t,\n-\t\t\t struct commit_list *parents,\n+\t\t\t const struct commit_list *parents,\n \t\t\t const char *msg, size_t msg_len,\n \t\t\t struct object_id *result_oid);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 30513e87bf..475646671a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1711,6 +1711,7 @@ static int try_to_commit(struct repository *r,\n \n out:\n \tfree_commit_extra_headers(extra);\n+\tfree_commit_list(parents);\n \tstrbuf_release(&err);\n \tstrbuf_release(&commit_msg);\n \tfree(amend_author);\ndiff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh\nindex a1911c4a9d..4f1d6e8ea6 100755\n--- a/t/t3403-rebase-skip.sh\n+++ b/t/t3403-rebase-skip.sh\n@@ -8,6 +8,7 @@ test_description='git rebase --merge --skip tests'\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nindex 1ee6b00fd5..515c949ae3 100755\n--- a/t/t3424-rebase-empty.sh\n+++ b/t/t3424-rebase-empty.sh\n@@ -2,6 +2,7 @@\n \n test_description='git rebase of commits that start or become empty'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup test repository' '\ndiff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh\nindex 9748443530..ead3fb4680 100755\n--- a/t/t3505-cherry-pick-empty.sh\n+++ b/t/t3505-cherry-pick-empty.sh\n@@ -5,6 +5,7 @@ test_description='test cherry-picking an empty commit'\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 test_expect_success setup '\ndiff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh\nindex 2128142a61..b88383df9e 100755\n--- a/t/t7505-prepare-commit-msg-hook.sh\n+++ b/t/t7505-prepare-commit-msg-hook.sh\n@@ -5,6 +5,7 @@ test_description='prepare-commit-msg hook'\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 test_expect_success 'set up commits for rebasing' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496137","messageId":"48bcd0ac80ee0b60eeda2bcedf55003a5049f289.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 20/29] sequencer: fix leaking string buffer in `commit_staged_changes()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:00Z","receivedAt":"2024-06-03T09:48:05Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're leaking the `rev` string buffer in various call paths. Refactor\nthe function to have a common exit path so that we can release its\nmemory reliably.\n\nThis fixes a subset of tests failing with the memory sanitizer in t3404.\nBut as there are more failures, we cannot yet mark the whole test suite\nas passing.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c | 105 ++++++++++++++++++++++++++++++++++------------------\n 1 file changed, 70 insertions(+), 35 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 475646671a..2ce807533f 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5146,33 +5146,47 @@ static int commit_staged_changes(struct repository *r,\n \tstruct replay_ctx *ctx = opts->ctx;\n \tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n \tunsigned int final_fixup = 0, is_clean;\n+\tstruct strbuf rev = STRBUF_INIT;\n+\tint ret;\n \n-\tif (has_unstaged_changes(r, 1))\n-\t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n+\tif (has_unstaged_changes(r, 1)) {\n+\t\tret = error(_(\"cannot rebase: You have unstaged changes.\"));\n+\t\tgoto out;\n+\t}\n \n \tis_clean = !has_uncommitted_changes(r, 0);\n \n \tif (!is_clean && !file_exists(rebase_path_message())) {\n \t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n-\n-\t\treturn error(_(staged_changes_advice), gpg_opt, gpg_opt);\n+\t\tret = error(_(staged_changes_advice), gpg_opt, gpg_opt);\n+\t\tgoto out;\n \t}\n+\n \tif (file_exists(rebase_path_amend())) {\n-\t\tstruct strbuf rev = STRBUF_INIT;\n \t\tstruct object_id head, to_amend;\n \n-\t\tif (repo_get_oid(r, \"HEAD\", &head))\n-\t\t\treturn error(_(\"cannot amend non-existing commit\"));\n-\t\tif (!read_oneliner(&rev, rebase_path_amend(), 0))\n-\t\t\treturn error(_(\"invalid file: '%s'\"), rebase_path_amend());\n-\t\tif (get_oid_hex(rev.buf, &to_amend))\n-\t\t\treturn error(_(\"invalid contents: '%s'\"),\n-\t\t\t\trebase_path_amend());\n-\t\tif (!is_clean && !oideq(&head, &to_amend))\n-\t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n-\t\t\t\t       \"working tree. Please, commit them\\n\"\n-\t\t\t\t       \"first and then run 'git rebase \"\n-\t\t\t\t       \"--continue' again.\"));\n+\t\tif (repo_get_oid(r, \"HEAD\", &head)) {\n+\t\t\tret = error(_(\"cannot amend non-existing commit\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!read_oneliner(&rev, rebase_path_amend(), 0)) {\n+\t\t\tret = error(_(\"invalid file: '%s'\"), rebase_path_amend());\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (get_oid_hex(rev.buf, &to_amend)) {\n+\t\t\tret = error(_(\"invalid contents: '%s'\"),\n+\t\t\t\t    rebase_path_amend());\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (!is_clean && !oideq(&head, &to_amend)) {\n+\t\t\tret = error(_(\"\\nYou have uncommitted changes in your \"\n+\t\t\t\t      \"working tree. Please, commit them\\n\"\n+\t\t\t\t      \"first and then run 'git rebase \"\n+\t\t\t\t      \"--continue' again.\"));\n+\t\t\tgoto out;\n+\t\t}\n \t\t/*\n \t\t * When skipping a failed fixup/squash, we need to edit the\n \t\t * commit message, the current fixup list and count, and if it\n@@ -5204,9 +5218,11 @@ static int commit_staged_changes(struct repository *r,\n \t\t\t\tlen--;\n \t\t\tstrbuf_setlen(&ctx->current_fixups, len);\n \t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n-\t\t\t\t\t  0) < 0)\n-\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n-\t\t\t\t\t     rebase_path_current_fixups());\n+\t\t\t\t\t  0) < 0) {\n+\t\t\t\tret = error(_(\"could not write file: '%s'\"),\n+\t\t\t\t\t    rebase_path_current_fixups());\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n \t\t\t/*\n \t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n@@ -5242,8 +5258,10 @@ static int commit_staged_changes(struct repository *r,\n \t\t\t\tconst char *path = rebase_path_squash_msg();\n \t\t\t\tconst char *encoding = get_commit_output_encoding();\n \n-\t\t\t\tif (parse_head(r, &commit))\n-\t\t\t\t\treturn error(_(\"could not parse HEAD\"));\n+\t\t\t\tif (parse_head(r, &commit)) {\n+\t\t\t\t\tret = error(_(\"could not parse HEAD\"));\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \n \t\t\t\tp = repo_logmsg_reencode(r, commit, NULL, encoding);\n \t\t\t\tif (!p)  {\n@@ -5259,12 +5277,13 @@ static int commit_staged_changes(struct repository *r,\n \t\t\t\t}\n \t\t\tunuse_commit_buffer:\n \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n-\t\t\t\tif (res)\n-\t\t\t\t\treturn res;\n+\t\t\t\tif (res) {\n+\t\t\t\t\tret = res;\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \t\t\t}\n \t\t}\n \n-\t\tstrbuf_release(&rev);\n \t\tflags |= AMEND_MSG;\n \t}\n \n@@ -5272,18 +5291,29 @@ static int commit_staged_changes(struct repository *r,\n \t\tif (refs_ref_exists(get_main_ref_store(r),\n \t\t\t\t    \"CHERRY_PICK_HEAD\") &&\n \t\t    refs_delete_ref(get_main_ref_store(r), \"\",\n-\t\t\t\t    \"CHERRY_PICK_HEAD\", NULL, REF_NO_DEREF))\n-\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n-\t\tif (unlink(git_path_merge_msg(r)) && errno != ENOENT)\n-\t\t\treturn error_errno(_(\"could not remove '%s'\"),\n-\t\t\t\t\t   git_path_merge_msg(r));\n-\t\tif (!final_fixup)\n-\t\t\treturn 0;\n+\t\t\t\t    \"CHERRY_PICK_HEAD\", NULL, REF_NO_DEREF)) {\n+\t\t\tret = error(_(\"could not remove CHERRY_PICK_HEAD\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (unlink(git_path_merge_msg(r)) && errno != ENOENT) {\n+\t\t\tret = error_errno(_(\"could not remove '%s'\"),\n+\t\t\t\t\t  git_path_merge_msg(r));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!final_fixup) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n-\t\t\t   opts, flags))\n-\t\treturn error(_(\"could not commit staged changes.\"));\n+\t\t\t   opts, flags)) {\n+\t\tret = error(_(\"could not commit staged changes.\"));\n+\t\tgoto out;\n+\t}\n+\n \tunlink(rebase_path_amend());\n \tunlink(git_path_merge_head(r));\n \trefs_delete_ref(get_main_ref_store(r), \"\", \"AUTO_MERGE\",\n@@ -5301,7 +5331,12 @@ static int commit_staged_changes(struct repository *r,\n \t\tstrbuf_reset(&ctx->current_fixups);\n \t\tctx->current_fixup_count = 0;\n \t}\n-\treturn 0;\n+\n+\tret = 0;\n+\n+out:\n+\tstrbuf_release(&rev);\n+\treturn ret;\n }\n \n int sequencer_continue(struct repository *r, struct replay_opts *opts)\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496138","messageId":"ebed18717f0f026d5f9bebb63d6edc40bd03cadb.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 21/29] apply: fix leaking string in `match_fragment()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:05Z","receivedAt":"2024-06-03T09:48:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before calling `update_pre_post_images()`, we call `strbuf_detach()` to\nput its buffer into a new string variable that we then pass to that\nfunction. Besides being rather pointless, it also causes us to leak\nmemory of that variable because we never free it.\n\nGet rid of the variable altogether and instead reach into the `strbuf`\ndirectly. While at it, refactor the code to have a common exit path and\nmark string that do not contain allocated memory as constant.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n apply.c                          | 87 ++++++++++++++++++++------------\n t/t3417-rebase-whitespace-fix.sh |  1 +\n 2 files changed, 56 insertions(+), 32 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex d8d26a48f1..fd7e3d649f 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -2494,18 +2494,21 @@ static int match_fragment(struct apply_state *state,\n \t\t\t  int match_beginning, int match_end)\n {\n \tint i;\n-\tchar *fixed_buf, *buf, *orig, *target;\n-\tstruct strbuf fixed;\n-\tsize_t fixed_len, postlen;\n+\tconst char *orig, *target;\n+\tstruct strbuf fixed = STRBUF_INIT;\n+\tsize_t postlen;\n \tint preimage_limit;\n+\tint ret;\n \n \tif (preimage->nr + current_lno <= img->nr) {\n \t\t/*\n \t\t * The hunk falls within the boundaries of img.\n \t\t */\n \t\tpreimage_limit = preimage->nr;\n-\t\tif (match_end && (preimage->nr + current_lno != img->nr))\n-\t\t\treturn 0;\n+\t\tif (match_end && (preimage->nr + current_lno != img->nr)) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t} else if (state->ws_error_action == correct_ws_error &&\n \t\t   (ws_rule & WS_BLANK_AT_EOF)) {\n \t\t/*\n@@ -2522,17 +2525,23 @@ static int match_fragment(struct apply_state *state,\n \t\t * we are not removing blanks at the end, so we\n \t\t * should reject the hunk at this position.\n \t\t */\n-\t\treturn 0;\n+\t\tret = 0;\n+\t\tgoto out;\n \t}\n \n-\tif (match_beginning && current_lno)\n-\t\treturn 0;\n+\tif (match_beginning && current_lno) {\n+\t\tret = 0;\n+\t\tgoto out;\n+\t}\n \n \t/* Quick hash check */\n-\tfor (i = 0; i < preimage_limit; i++)\n+\tfor (i = 0; i < preimage_limit; i++) {\n \t\tif ((img->line[current_lno + i].flag & LINE_PATCHED) ||\n-\t\t    (preimage->line[i].hash != img->line[current_lno + i].hash))\n-\t\t\treturn 0;\n+\t\t    (preimage->line[i].hash != img->line[current_lno + i].hash)) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n \n \tif (preimage_limit == preimage->nr) {\n \t\t/*\n@@ -2545,8 +2554,10 @@ static int match_fragment(struct apply_state *state,\n \t\tif ((match_end\n \t\t     ? (current + preimage->len == img->len)\n \t\t     : (current + preimage->len <= img->len)) &&\n-\t\t    !memcmp(img->buf + current, preimage->buf, preimage->len))\n-\t\t\treturn 1;\n+\t\t    !memcmp(img->buf + current, preimage->buf, preimage->len)) {\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n+\t\t}\n \t} else {\n \t\t/*\n \t\t * The preimage extends beyond the end of img, so\n@@ -2555,7 +2566,7 @@ static int match_fragment(struct apply_state *state,\n \t\t * There must be one non-blank context line that match\n \t\t * a line before the end of img.\n \t\t */\n-\t\tchar *buf_end;\n+\t\tconst char *buf, *buf_end;\n \n \t\tbuf = preimage->buf;\n \t\tbuf_end = buf;\n@@ -2565,8 +2576,10 @@ static int match_fragment(struct apply_state *state,\n \t\tfor ( ; buf < buf_end; buf++)\n \t\t\tif (!isspace(*buf))\n \t\t\t\tbreak;\n-\t\tif (buf == buf_end)\n-\t\t\treturn 0;\n+\t\tif (buf == buf_end) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -2574,12 +2587,16 @@ static int match_fragment(struct apply_state *state,\n \t * fuzzy matching. We collect all the line length information because\n \t * we need it to adjust whitespace if we match.\n \t */\n-\tif (state->ws_ignore_action == ignore_ws_change)\n-\t\treturn line_by_line_fuzzy_match(img, preimage, postimage,\n-\t\t\t\t\t\tcurrent, current_lno, preimage_limit);\n+\tif (state->ws_ignore_action == ignore_ws_change) {\n+\t\tret = line_by_line_fuzzy_match(img, preimage, postimage,\n+\t\t\t\t\t       current, current_lno, preimage_limit);\n+\t\tgoto out;\n+\t}\n \n-\tif (state->ws_error_action != correct_ws_error)\n-\t\treturn 0;\n+\tif (state->ws_error_action != correct_ws_error) {\n+\t\tret = 0;\n+\t\tgoto out;\n+\t}\n \n \t/*\n \t * The hunk does not apply byte-by-byte, but the hash says\n@@ -2608,7 +2625,7 @@ static int match_fragment(struct apply_state *state,\n \t * but in this loop we will only handle the part of the\n \t * preimage that falls within the file.\n \t */\n-\tstrbuf_init(&fixed, preimage->len + 1);\n+\tstrbuf_grow(&fixed, preimage->len + 1);\n \torig = preimage->buf;\n \ttarget = img->buf + current;\n \tfor (i = 0; i < preimage_limit; i++) {\n@@ -2644,8 +2661,10 @@ static int match_fragment(struct apply_state *state,\n \t\t\tpostlen += tgtfix.len;\n \n \t\tstrbuf_release(&tgtfix);\n-\t\tif (!match)\n-\t\t\tgoto unmatch_exit;\n+\t\tif (!match) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \n \t\torig += oldlen;\n \t\ttarget += tgtlen;\n@@ -2666,9 +2685,13 @@ static int match_fragment(struct apply_state *state,\n \t\t/* Try fixing the line in the preimage */\n \t\tws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL);\n \n-\t\tfor (j = fixstart; j < fixed.len; j++)\n-\t\t\tif (!isspace(fixed.buf[j]))\n-\t\t\t\tgoto unmatch_exit;\n+\t\tfor (j = fixstart; j < fixed.len; j++) {\n+\t\t\tif (!isspace(fixed.buf[j])) {\n+\t\t\t\tret = 0;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\n \n \t\torig += oldlen;\n \t}\n@@ -2678,16 +2701,16 @@ static int match_fragment(struct apply_state *state,\n \t * has whitespace breakages unfixed, and fixing them makes the\n \t * hunk match.  Update the context lines in the postimage.\n \t */\n-\tfixed_buf = strbuf_detach(&fixed, &fixed_len);\n \tif (postlen < postimage->len)\n \t\tpostlen = 0;\n \tupdate_pre_post_images(preimage, postimage,\n-\t\t\t       fixed_buf, fixed_len, postlen);\n-\treturn 1;\n+\t\t\t       fixed.buf, fixed.len, postlen);\n \n- unmatch_exit:\n+\tret = 1;\n+\n+out:\n \tstrbuf_release(&fixed);\n-\treturn 0;\n+\treturn ret;\n }\n \n static int find_pos(struct apply_state *state,\ndiff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh\nindex 96f2cf22fa..22ee3a2045 100755\n--- a/t/t3417-rebase-whitespace-fix.sh\n+++ b/t/t3417-rebase-whitespace-fix.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --whitespace=fix\n This test runs git rebase --whitespace=fix and make sure that it works.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # prepare initial revision of \"file\" with a blank line at the end\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496139","messageId":"4f4ae5ea96e023e264322a7adf1f1080b478a1e2.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 22/29] builtin/clone: plug leaking HEAD ref in `wanted_peer_refs()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:10Z","receivedAt":"2024-06-03T09:48:14Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `wanted_peer_refs()` we first create a copy of the \"HEAD\" ref. This\ncopy may not actually be passed back to the caller, but is not getting\nfreed in this case. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c          | 3 ++-\n t/t5300-pack-object.sh   | 4 ++--\n t/t5305-include-tag.sh   | 1 +\n t/t5612-clone-refspec.sh | 1 +\n 4 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 730b3efae6..ae9863ed47 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -530,7 +530,8 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\tif (!option_branch)\n \t\t\tremote_head = guess_remote_head(head, refs, 0);\n \t\telse {\n-\t\t\tlocal_refs = NULL;\n+\t\t\tfree_one_ref(head);\n+\t\t\tlocal_refs = head = NULL;\n \t\t\ttail = &local_refs;\n \t\t\tremote_head = copy_ref(find_remote_branch(refs, option_branch));\n \t\t}\ndiff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh\nindex 61e2be2903..4ad023c846 100755\n--- a/t/t5300-pack-object.sh\n+++ b/t/t5300-pack-object.sh\n@@ -3,9 +3,9 @@\n # Copyright (c) 2005 Junio C Hamano\n #\n \n-test_description='git pack-object\n+test_description='git pack-object'\n \n-'\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t5305-include-tag.sh b/t/t5305-include-tag.sh\nindex 44bd9ef45f..dc8fe55c82 100755\n--- a/t/t5305-include-tag.sh\n+++ b/t/t5305-include-tag.sh\n@@ -4,6 +4,7 @@ test_description='git pack-object --include-tag'\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 TRASH=$(pwd)\ndiff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh\nindex 3126cfd7e9..72762de977 100755\n--- a/t/t5612-clone-refspec.sh\n+++ b/t/t5612-clone-refspec.sh\n@@ -4,6 +4,7 @@ test_description='test refspec written by clone-command'\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 test_expect_success 'setup' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496140","messageId":"713531c6f18a8456b34061a49abd76ec5c61d8c1.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 23/29] sequencer: fix memory leaks in `make_script_with_merges()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:15Z","receivedAt":"2024-06-03T09:48:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Fix some trivial memory leaks in `make_script_with_merges()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c                       | 3 +++\n t/t3418-rebase-continue.sh        | 1 +\n t/t3421-rebase-topology-linear.sh | 2 ++\n t/t3434-rebase-i18n.sh            | 1 +\n 4 files changed, 7 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 2ce807533f..1a54f18b43 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5951,6 +5951,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \tstrbuf_release(&oneline);\n \tstrbuf_release(&buf);\n \n+\toidset_clear(&interesting);\n+\toidset_clear(&child_seen);\n+\toidset_clear(&shown);\n \toidmap_free(&commit2todo, 1);\n \toidmap_free(&state.commit2label, 1);\n \thashmap_clear_and_free(&state.labels, struct labels_entry, entry);\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 127216f722..c0d29c2154 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --continue tests'\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 62d86d557d..737af80bb3 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='basic rebase topology tests'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-rebase.sh\n \ndiff --git a/t/t3434-rebase-i18n.sh b/t/t3434-rebase-i18n.sh\nindex a4e482d2cd..26a48d6b10 100755\n--- a/t/t3434-rebase-i18n.sh\n+++ b/t/t3434-rebase-i18n.sh\n@@ -17,6 +17,7 @@ Initial setup:\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 compare_msg () {\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496141","messageId":"eeef280dd5f17716ad7c4c975dd26ce957a78c52.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 24/29] builtin/merge: fix leaking `struct cmdnames` in `get_strategy()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:20Z","receivedAt":"2024-06-03T09:48:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"builtin/merge.c\" we use the helper infrastructure to figure out what\nmerge strategies there are. We never free contents of the `cmdnames`\nstructures though and thus leak their memory.\n\nFix this by exposing the already existing `clean_cmdnames()` function to\nrelease their memory. As this name isn't quite idiomatic, rename it to\n`cmdnames_release()` while at it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c         | 10 +++++++---\n help.c                  | 12 ++++++------\n help.h                  |  2 ++\n t/t7606-merge-custom.sh |  1 +\n 4 files changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 50b0c87a95..682c6ed868 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -164,7 +164,7 @@ static struct strategy *get_strategy(const char *name)\n {\n \tint i;\n \tstruct strategy *ret;\n-\tstatic struct cmdnames main_cmds, other_cmds;\n+\tstatic struct cmdnames main_cmds = {0}, other_cmds = {0};\n \tstatic int loaded;\n \tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \n@@ -182,10 +182,9 @@ static struct strategy *get_strategy(const char *name)\n \t\t\treturn &all_strategy[i];\n \n \tif (!loaded) {\n-\t\tstruct cmdnames not_strategies;\n+\t\tstruct cmdnames not_strategies = {0};\n \t\tloaded = 1;\n \n-\t\tmemset(&not_strategies, 0, sizeof(struct cmdnames));\n \t\tload_command_list(\"git-merge-\", &main_cmds, &other_cmds);\n \t\tfor (i = 0; i < main_cmds.cnt; i++) {\n \t\t\tint j, found = 0;\n@@ -197,6 +196,8 @@ static struct strategy *get_strategy(const char *name)\n \t\t\t\tadd_cmdname(&not_strategies, ent->name, ent->len);\n \t\t}\n \t\texclude_cmds(&main_cmds, &not_strategies);\n+\n+\t\tcmdnames_release(&not_strategies);\n \t}\n \tif (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {\n \t\tfprintf(stderr, _(\"Could not find merge strategy '%s'.\\n\"), name);\n@@ -216,6 +217,9 @@ static struct strategy *get_strategy(const char *name)\n \tCALLOC_ARRAY(ret, 1);\n \tret->name = xstrdup(name);\n \tret->attr = NO_TRIVIAL;\n+\n+\tcmdnames_release(&main_cmds);\n+\tcmdnames_release(&other_cmds);\n \treturn ret;\n }\n \ndiff --git a/help.c b/help.c\nindex 1d057aa607..3686285ca3 100644\n--- a/help.c\n+++ b/help.c\n@@ -157,7 +157,7 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len)\n \tcmds->names[cmds->cnt++] = ent;\n }\n \n-static void clean_cmdnames(struct cmdnames *cmds)\n+void cmdnames_release(struct cmdnames *cmds)\n {\n \tint i;\n \tfor (i = 0; i < cmds->cnt; ++i)\n@@ -359,8 +359,8 @@ void list_all_main_cmds(struct string_list *list)\n \tfor (i = 0; i < main_cmds.cnt; i++)\n \t\tstring_list_append(list, main_cmds.names[i]->name);\n \n-\tclean_cmdnames(&main_cmds);\n-\tclean_cmdnames(&other_cmds);\n+\tcmdnames_release(&main_cmds);\n+\tcmdnames_release(&other_cmds);\n }\n \n void list_all_other_cmds(struct string_list *list)\n@@ -375,8 +375,8 @@ void list_all_other_cmds(struct string_list *list)\n \tfor (i = 0; i < other_cmds.cnt; i++)\n \t\tstring_list_append(list, other_cmds.names[i]->name);\n \n-\tclean_cmdnames(&main_cmds);\n-\tclean_cmdnames(&other_cmds);\n+\tcmdnames_release(&main_cmds);\n+\tcmdnames_release(&other_cmds);\n }\n \n void list_cmds_by_category(struct string_list *list,\n@@ -689,7 +689,7 @@ const char *help_unknown_cmd(const char *cmd)\n \tif (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {\n \t\tconst char *assumed = main_cmds.names[0]->name;\n \t\tmain_cmds.names[0] = NULL;\n-\t\tclean_cmdnames(&main_cmds);\n+\t\tcmdnames_release(&main_cmds);\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"WARNING: You called a Git command named '%s', \"\n \t\t\t     \"which does not exist.\"),\ndiff --git a/help.h b/help.h\nindex af073a7a02..e716ee27ea 100644\n--- a/help.h\n+++ b/help.h\n@@ -13,6 +13,8 @@ struct cmdnames {\n \t} **names;\n };\n \n+void cmdnames_release(struct cmdnames *cmds);\n+\n static inline void mput_char(char c, unsigned int num)\n {\n \twhile (num--)\ndiff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh\nindex 81fb7c474c..135cb23085 100755\n--- a/t/t7606-merge-custom.sh\n+++ b/t/t7606-merge-custom.sh\n@@ -14,6 +14,7 @@ Testing a custom strategy.\n * (tag: c0) c0\n \"\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'set up custom strategy' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496142","messageId":"5441f1e2ef3f33840588e623386c685b890ab052.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 25/29] merge: fix leaking merge bases","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:25Z","receivedAt":"2024-06-03T09:48:28Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When calling either the recursive or the ORT merge machineries we need\nto provide a list of merge bases. The ownership of that parameter is\nthen implicitly transferred to the callee, which is somewhat fishy.\nFurthermore, that list may leak in some cases where the merge machinery\nruns into an error, thus causing a memory leak.\n\nRefactor the code such that we stop transferring ownership. Instead, the\nmerge machinery will now create its own local copies of the passed in\nlist as required if they need to modify the list. Free the list at the\ncallsites as required.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge-tree.c       |  1 +\n builtin/merge.c            |  2 ++\n commit.c                   |  2 +-\n commit.h                   |  2 +-\n log-tree.c                 |  1 +\n merge-ort-wrappers.c       |  2 +-\n merge-ort-wrappers.h       |  2 +-\n merge-ort.c                | 12 ++++++----\n merge-ort.h                |  2 +-\n merge-recursive.c          | 49 +++++++++++++++++++++++---------------\n merge-recursive.h          |  2 +-\n sequencer.c                |  1 +\n t/t3430-rebase-merges.sh   |  1 +\n t/t6402-merge-rename.sh    |  1 +\n t/t6430-merge-recursive.sh |  1 +\n t/t6436-merge-overwrite.sh |  1 +\n t/t7611-merge-abort.sh     |  1 +\n 17 files changed, 54 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 1082d919fd..dab2fdc2a6 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -482,6 +482,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\t\tdie(_(\"refusing to merge unrelated histories\"));\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\t\tfree_commit_list(merge_bases);\n \t}\n \n \tif (result.clean < 0)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 682c6ed868..1120a6e2f8 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -746,6 +746,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\telse\n \t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n \t\t\t\t\t\treversed, &result);\n+\t\tfree_commit_list(reversed);\n+\n \t\tif (clean < 0) {\n \t\t\trollback_lock_file(&lock);\n \t\t\treturn 2;\ndiff --git a/commit.c b/commit.c\nindex f674eca320..1386b12df1 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -680,7 +680,7 @@ unsigned commit_list_count(const struct commit_list *l)\n \treturn c;\n }\n \n-struct commit_list *copy_commit_list(struct commit_list *list)\n+struct commit_list *copy_commit_list(const struct commit_list *list)\n {\n \tstruct commit_list *head = NULL;\n \tstruct commit_list **pp = &head;\ndiff --git a/commit.h b/commit.h\nindex 442e50ff24..acabb05785 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -181,7 +181,7 @@ struct commit_list *commit_list_insert_by_date(struct commit *item,\n void commit_list_sort_by_date(struct commit_list **list);\n \n /* Shallow copy of the input list */\n-struct commit_list *copy_commit_list(struct commit_list *list);\n+struct commit_list *copy_commit_list(const struct commit_list *list);\n \n /* Modify list in-place to reverse it, returning new head; list will be tail */\n struct commit_list *reverse_commit_list(struct commit_list *list);\ndiff --git a/log-tree.c b/log-tree.c\nindex 41416de4e3..91b96d83a0 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1047,6 +1047,7 @@ static int do_remerge_diff(struct rev_info *opt,\n \tlog_tree_diff_flush(opt);\n \n \t/* Cleanup */\n+\tfree_commit_list(bases);\n \tcleanup_additional_headers(&opt->diffopt);\n \tstrbuf_release(&parent1_desc);\n \tstrbuf_release(&parent2_desc);\ndiff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c\nindex 4acedf3c33..d6f6135996 100644\n--- a/merge-ort-wrappers.c\n+++ b/merge-ort-wrappers.c\n@@ -48,7 +48,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,\n int merge_ort_recursive(struct merge_options *opt,\n \t\t\tstruct commit *side1,\n \t\t\tstruct commit *side2,\n-\t\t\tstruct commit_list *merge_bases,\n+\t\t\tconst struct commit_list *merge_bases,\n \t\t\tstruct commit **result)\n {\n \tstruct tree *head = repo_get_commit_tree(opt->repo, side1);\ndiff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h\nindex 0c4c57adbb..90af1f69c5 100644\n--- a/merge-ort-wrappers.h\n+++ b/merge-ort-wrappers.h\n@@ -19,7 +19,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,\n int merge_ort_recursive(struct merge_options *opt,\n \t\t\tstruct commit *h1,\n \t\t\tstruct commit *h2,\n-\t\t\tstruct commit_list *ancestors,\n+\t\t\tconst struct commit_list *ancestors,\n \t\t\tstruct commit **result);\n \n #endif\ndiff --git a/merge-ort.c b/merge-ort.c\nindex eaede6cead..8ed8a4c9dc 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -5071,11 +5071,12 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,\n  * Originally from merge_recursive_internal(); somewhat adapted, though.\n  */\n static void merge_ort_internal(struct merge_options *opt,\n-\t\t\t       struct commit_list *merge_bases,\n+\t\t\t       const struct commit_list *_merge_bases,\n \t\t\t       struct commit *h1,\n \t\t\t       struct commit *h2,\n \t\t\t       struct merge_result *result)\n {\n+\tstruct commit_list *merge_bases = copy_commit_list(_merge_bases);\n \tstruct commit *next;\n \tstruct commit *merged_merge_bases;\n \tconst char *ancestor_name;\n@@ -5085,7 +5086,7 @@ static void merge_ort_internal(struct merge_options *opt,\n \t\tif (repo_get_merge_bases(the_repository, h1, h2,\n \t\t\t\t\t &merge_bases) < 0) {\n \t\t\tresult->clean = -1;\n-\t\t\treturn;\n+\t\t\tgoto out;\n \t\t}\n \t\t/* See merge-ort.h:merge_incore_recursive() declaration NOTE */\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n@@ -5129,7 +5130,7 @@ static void merge_ort_internal(struct merge_options *opt,\n \t\topt->branch2 = \"Temporary merge branch 2\";\n \t\tmerge_ort_internal(opt, NULL, prev, next, result);\n \t\tif (result->clean < 0)\n-\t\t\treturn;\n+\t\t\tgoto out;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->priv->call_depth--;\n@@ -5152,6 +5153,9 @@ static void merge_ort_internal(struct merge_options *opt,\n \t\t\t\t\tresult);\n \tstrbuf_release(&merge_base_abbrev);\n \topt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */\n+\n+out:\n+\tfree_commit_list(merge_bases);\n }\n \n void merge_incore_nonrecursive(struct merge_options *opt,\n@@ -5181,7 +5185,7 @@ void merge_incore_nonrecursive(struct merge_options *opt,\n }\n \n void merge_incore_recursive(struct merge_options *opt,\n-\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    const struct commit_list *merge_bases,\n \t\t\t    struct commit *side1,\n \t\t\t    struct commit *side2,\n \t\t\t    struct merge_result *result)\ndiff --git a/merge-ort.h b/merge-ort.h\nindex ce56ec1a78..6af97c0828 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -59,7 +59,7 @@ struct merge_result {\n  *           first\", 2006-08-09)\n  */\n void merge_incore_recursive(struct merge_options *opt,\n-\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    const struct commit_list *merge_bases,\n \t\t\t    struct commit *side1,\n \t\t\t    struct commit *side2,\n \t\t\t    struct merge_result *result);\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 832c8ef3f3..1ac0316cce 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3633,15 +3633,16 @@ static int merge_trees_internal(struct merge_options *opt,\n static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t    struct commit *h1,\n \t\t\t\t    struct commit *h2,\n-\t\t\t\t    struct commit_list *merge_bases,\n+\t\t\t\t    const struct commit_list *_merge_bases,\n \t\t\t\t    struct commit **result)\n {\n+\tstruct commit_list *merge_bases = copy_commit_list(_merge_bases);\n \tstruct commit_list *iter;\n \tstruct commit *merged_merge_bases;\n \tstruct tree *result_tree;\n-\tint clean;\n \tconst char *ancestor_name;\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n+\tint ret;\n \n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n@@ -3651,8 +3652,10 @@ static int merge_recursive_internal(struct merge_options *opt,\n \n \tif (!merge_bases) {\n \t\tif (repo_get_merge_bases(the_repository, h1, h2,\n-\t\t\t\t\t &merge_bases) < 0)\n-\t\t\treturn -1;\n+\t\t\t\t\t &merge_bases) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \n@@ -3702,14 +3705,18 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n \t\tif (merge_recursive_internal(opt, merged_merge_bases, iter->item,\n-\t\t\t\t\t     NULL, &merged_merge_bases) < 0)\n-\t\t\treturn -1;\n+\t\t\t\t\t     NULL, &merged_merge_bases) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->priv->call_depth--;\n \n-\t\tif (!merged_merge_bases)\n-\t\t\treturn err(opt, _(\"merge returned no commit\"));\n+\t\tif (!merged_merge_bases) {\n+\t\t\tret = err(opt, _(\"merge returned no commit\"));\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -3726,17 +3733,16 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = ancestor_name;\n-\tclean = merge_trees_internal(opt,\n-\t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n-\t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n-\t\t\t\t     repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t\t  merged_merge_bases),\n-\t\t\t\t     &result_tree);\n-\tstrbuf_release(&merge_base_abbrev);\n+\tret = merge_trees_internal(opt,\n+\t\t\t\t   repo_get_commit_tree(opt->repo, h1),\n+\t\t\t\t   repo_get_commit_tree(opt->repo, h2),\n+\t\t\t\t   repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t\tmerged_merge_bases),\n+\t\t\t\t   &result_tree);\n \topt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */\n-\tif (clean < 0) {\n+\tif (ret < 0) {\n \t\tflush_output(opt);\n-\t\treturn clean;\n+\t\tgoto out;\n \t}\n \n \tif (opt->priv->call_depth) {\n@@ -3745,7 +3751,11 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-\treturn clean;\n+\n+out:\n+\tstrbuf_release(&merge_base_abbrev);\n+\tfree_commit_list(merge_bases);\n+\treturn ret;\n }\n \n static int merge_start(struct merge_options *opt, struct tree *head)\n@@ -3827,7 +3837,7 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *merge_bases,\n+\t\t    const struct commit_list *merge_bases,\n \t\t    struct commit **result)\n {\n \tint clean;\n@@ -3895,6 +3905,7 @@ int merge_recursive_generic(struct merge_options *opt,\n \trepo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);\n \tclean = merge_recursive(opt, head_commit, next_commit, ca,\n \t\t\t\tresult);\n+\tfree_commit_list(ca);\n \tif (clean < 0) {\n \t\trollback_lock_file(&lock);\n \t\treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 839eb6436e..3136c7cc2d 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -104,7 +104,7 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *merge_bases,\n+\t\t    const struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n /*\ndiff --git a/sequencer.c b/sequencer.c\nindex 1a54f18b43..9e90084692 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4315,6 +4315,7 @@ static int do_merge(struct repository *r,\n \tstrbuf_release(&ref_name);\n \trollback_lock_file(&lock);\n \tfree_commit_list(to_merge);\n+\tfree_commit_list(bases);\n \treturn ret;\n }\n \ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 59b5d6b6f2..36ca126bcd 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -21,6 +21,7 @@ Initial setup:\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\n . \"$TEST_DIRECTORY\"/lib-log-graph.sh\ndiff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh\nindex 2738b50c2a..729aac9842 100755\n--- a/t/t6402-merge-rename.sh\n+++ b/t/t6402-merge-rename.sh\n@@ -4,6 +4,7 @@ test_description='Merge-recursive merging renames'\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 modify () {\ndiff --git a/t/t6430-merge-recursive.sh b/t/t6430-merge-recursive.sh\nindex ca15e6dd6d..555f00f78a 100755\n--- a/t/t6430-merge-recursive.sh\n+++ b/t/t6430-merge-recursive.sh\n@@ -5,6 +5,7 @@ test_description='merge-recursive backend test'\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 . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t6436-merge-overwrite.sh b/t/t6436-merge-overwrite.sh\nindex 4f4376421e..ccc620477d 100755\n--- a/t/t6436-merge-overwrite.sh\n+++ b/t/t6436-merge-overwrite.sh\n@@ -7,6 +7,7 @@ Do not overwrite changes.'\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 test_expect_success 'setup' '\ndiff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh\nindex d6975ca48d..992a8f9874 100755\n--- a/t/t7611-merge-abort.sh\n+++ b/t/t7611-merge-abort.sh\n@@ -25,6 +25,7 @@ Next, test git merge --abort with the following variables:\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 test_expect_success 'setup' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496143","messageId":"ba9c2b4ea73be5e9c2529921b6491b9baaf661d5.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 26/29] line-range: plug leaking find functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:30Z","receivedAt":"2024-06-03T09:48:34Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `parse_range_funcname()` we may end up allocating a \"find function\",\nbut never free it. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-range.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/line-range.c b/line-range.c\nindex 60f0e5ada8..b99f0d9895 100644\n--- a/line-range.c\n+++ b/line-range.c\n@@ -234,6 +234,8 @@ static const char *parse_range_funcname(\n \t}\n \n \tregfree(&regexp);\n+\tif (xecfg)\n+\t\txdiff_clear_find_func(xecfg);\n \tfree(xecfg);\n \tfree(pattern);\n \n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496144","messageId":"5f2b0adc5621f741442ff41a916caf9e1424afbf.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 27/29] blame: fix leaking data for blame scoreboards","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:35Z","receivedAt":"2024-06-03T09:48:39Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are some memory leaks when cleaning up blame scoreboards. Fix\nthose.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n blame.c                           | 4 ++++\n t/t4061-diff-indent.sh            | 1 +\n t/t8002-blame.sh                  | 1 +\n t/t8004-blame-with-conflicts.sh   | 1 +\n t/t8006-blame-textconv.sh         | 2 ++\n t/t8009-blame-vs-topicbranches.sh | 2 ++\n t/t8011-blame-split-file.sh       | 2 ++\n t/t8012-blame-colors.sh           | 1 +\n t/t8014-blame-ignore-fuzzy.sh     | 2 ++\n 9 files changed, 16 insertions(+)\n\ndiff --git a/blame.c b/blame.c\nindex 33586b9777..f97d0e9e1a 100644\n--- a/blame.c\n+++ b/blame.c\n@@ -2928,6 +2928,10 @@ void setup_blame_bloom_data(struct blame_scoreboard *sb)\n \n void cleanup_scoreboard(struct blame_scoreboard *sb)\n {\n+\tfree(sb->lineno);\n+\tclear_prio_queue(&sb->commits);\n+\toidset_clear(&sb->ignore_list);\n+\n \tif (sb->bloom_data) {\n \t\tint i;\n \t\tfor (i = 0; i < sb->bloom_data->nr; i++) {\ndiff --git a/t/t4061-diff-indent.sh b/t/t4061-diff-indent.sh\nindex 7750b87ca1..2942e5d9b9 100755\n--- a/t/t4061-diff-indent.sh\n+++ b/t/t4061-diff-indent.sh\n@@ -6,6 +6,7 @@ test_description='Test diff indent heuristic.\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 . \"$TEST_DIRECTORY\"/lib-diff.sh\n \ndiff --git a/t/t8002-blame.sh b/t/t8002-blame.sh\nindex 0147de304b..3596634039 100755\n--- a/t/t8002-blame.sh\n+++ b/t/t8002-blame.sh\n@@ -5,6 +5,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n PROG='git blame -c'\ndiff --git a/t/t8004-blame-with-conflicts.sh b/t/t8004-blame-with-conflicts.sh\nindex 35414a5336..2c2a0b33f9 100755\n--- a/t/t8004-blame-with-conflicts.sh\n+++ b/t/t8004-blame-with-conflicts.sh\n@@ -6,6 +6,7 @@ test_description='git blame on conflicted files'\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 test_expect_success 'setup first case' '\ndiff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh\nindex 7683515155..42f8be25a3 100755\n--- a/t/t8006-blame-textconv.sh\n+++ b/t/t8006-blame-textconv.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='git blame textconv support'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n find_blame() {\ndiff --git a/t/t8009-blame-vs-topicbranches.sh b/t/t8009-blame-vs-topicbranches.sh\nindex 72596e38b2..30331713b9 100755\n--- a/t/t8009-blame-vs-topicbranches.sh\n+++ b/t/t8009-blame-vs-topicbranches.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='blaming trough history with topic branches'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Creates the history shown below. '*'s mark the first parent in the merges.\ndiff --git a/t/t8011-blame-split-file.sh b/t/t8011-blame-split-file.sh\nindex bdda0c03fe..da1801f4d2 100755\n--- a/t/t8011-blame-split-file.sh\n+++ b/t/t8011-blame-split-file.sh\n@@ -10,6 +10,8 @@ Note that we need to use \"blame -C\" to find the commit for all lines. We will\n not bother testing that the non-C case fails to find it. That is how blame\n behaves now, but it is not a property we want to make sure is retained.\n '\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # help avoid typing and reading long strings of similar lines\ndiff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh\nindex c3a5f6d01f..9a79c109f2 100755\n--- a/t/t8012-blame-colors.sh\n+++ b/t/t8012-blame-colors.sh\n@@ -5,6 +5,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n PROG='git blame -c'\ndiff --git a/t/t8014-blame-ignore-fuzzy.sh b/t/t8014-blame-ignore-fuzzy.sh\nindex 0bd0341301..933222cea1 100755\n--- a/t/t8014-blame-ignore-fuzzy.sh\n+++ b/t/t8014-blame-ignore-fuzzy.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='git blame ignore fuzzy heuristic'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n pick_author='s/^[0-9a-f^]* *(\\([^ ]*\\) .*/\\1/'\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496145","messageId":"7ee87344377cecb8744698d9690191b156fe9b2d.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 28/29] builtin/blame: fix leaking prefixed paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:39Z","receivedAt":"2024-06-03T09:48:44Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `cmd_blame()` we compute prefixed paths by calling `add_prefix()`,\nwhich itself calls `prefix_path()`. While `prefix_path()` returns an\nallocated string, `add_prefix()` pretends to return a constant string.\nConsequently, this path never gets freed.\n\nFix the return type to be `char *` and free the path to plug the memory\nleak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/blame.c               | 5 +++--\n t/t6130-pathspec-noglob.sh    | 2 ++\n t/t7010-setup.sh              | 1 +\n t/t8003-blame-corner-cases.sh | 1 +\n t/t8008-blame-formats.sh      | 2 ++\n 5 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex e09ff0155a..17694410ed 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -687,7 +687,7 @@ static unsigned parse_score(const char *arg)\n \treturn score;\n }\n \n-static const char *add_prefix(const char *prefix, const char *path)\n+static char *add_prefix(const char *prefix, const char *path)\n {\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n@@ -865,7 +865,7 @@ static void build_ignorelist(struct blame_scoreboard *sb,\n int cmd_blame(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n-\tconst char *path;\n+\tchar *path = NULL;\n \tstruct blame_scoreboard sb;\n \tstruct blame_origin *o;\n \tstruct blame_entry *ent = NULL;\n@@ -1226,6 +1226,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t}\n \n cleanup:\n+\tfree(path);\n \tcleanup_scoreboard(&sb);\n \trelease_revisions(&revs);\n \treturn 0;\ndiff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh\nindex ba7902c9cd..82de25d549 100755\n--- a/t/t6130-pathspec-noglob.sh\n+++ b/t/t6130-pathspec-noglob.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test globbing (and noglob) of pathspec limiting'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'create commits with glob characters' '\ndiff --git a/t/t7010-setup.sh b/t/t7010-setup.sh\nindex 520f96d09f..d9add2162e 100755\n--- a/t/t7010-setup.sh\n+++ b/t/t7010-setup.sh\n@@ -2,6 +2,7 @@\n \n test_description='setup taking and sanitizing funny paths'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh\nindex 731265541a..6288352f57 100755\n--- a/t/t8003-blame-corner-cases.sh\n+++ b/t/t8003-blame-corner-cases.sh\n@@ -4,6 +4,7 @@ test_description='git blame corner cases'\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 pick_fc='s/^[0-9a-f^]* *\\([^ ]*\\) *(\\([^ ]*\\) .*/\\1-\\2/'\ndiff --git a/t/t8008-blame-formats.sh b/t/t8008-blame-formats.sh\nindex ae4b579d24..fb5d225a67 100755\n--- a/t/t8008-blame-formats.sh\n+++ b/t/t8008-blame-formats.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='blame output in various formats on a simple case'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496146","messageId":"5b5fdfc6cb8262d1d975de41d0643fd69078bbfa.1717402439.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH 29/29] builtin/blame: fix leaking ignore revs files","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-03T09:48:44Z","receivedAt":"2024-06-03T09:48:48Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When parsing the blame configuration we add \"blame.ignoreRevsFile\"\nconfigs to a string list. This string list is declared as with `NODUP`,\nand thus we hand over the allocated string to that list. We eventually\nend up calling `string_list_clear()` on that list, but due to it being\ndeclared as `NODUP` we will not release the associated strings and thus\nleak memory.\n\nFix this issue by setting up the list as `DUP` instead and free the\nconfig string after insertion.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/blame.c              | 3 ++-\n t/t8013-blame-ignore-revs.sh | 2 ++\n 2 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 17694410ed..702fe4fb94 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -67,7 +67,7 @@ static int no_whole_file_rename;\n static int show_progress;\n static char repeated_meta_color[COLOR_MAXLEN];\n static int coloring_mode;\n-static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;\n+static struct string_list ignore_revs_file_list = STRING_LIST_INIT_DUP;\n static int mark_unblamable_lines;\n static int mark_ignored_lines;\n \n@@ -725,6 +725,7 @@ static int git_blame_config(const char *var, const char *value,\n \t\tif (ret)\n \t\t\treturn ret;\n \t\tstring_list_insert(&ignore_revs_file_list, str);\n+\t\tfree(str);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"blame.markunblamablelines\")) {\ndiff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh\nindex dbfbd86e83..d33788d867 100755\n--- a/t/t8013-blame-ignore-revs.sh\n+++ b/t/t8013-blame-ignore-revs.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='ignore revisions when blaming'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Creates:\n-- \n2.45.1.410.g58bac47f8e.dirty\n\n"},{"id":"496150","messageId":"758f2321-dc63-4209-8b54-99b74b6bb897@gmail.com","threadId":"61578","inReplyTo":"48bcd0ac80ee0b60eeda2bcedf55003a5049f289.1717402439.git.ps@pks.im","subject":"Re: [PATCH 20/29] sequencer: fix leaking string buffer in `commit_staged_changes()`","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-06-03T13:14:20Z","receivedAt":"2024-06-03T13:14:23Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 03/06/2024 10:48, Patrick Steinhardt wrote:\n> @@ -5259,12 +5277,13 @@ static int commit_staged_changes(struct repository *r,\n>   \t\t\t\t}\n>   \t\t\tunuse_commit_buffer:\n>   \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n> -\t\t\t\tif (res)\n> -\t\t\t\t\treturn res;\n> +\t\t\t\tif (res) {\n> +\t\t\t\t\tret = res;\n> +\t\t\t\t\tgoto out;\n> +\t\t\t\t}\n\nHaving 'ret' and 'res' in this block is a bit confusing - we could \ndelete the declaration for 'res' and  either replace its use with 'ret', \nor rename 'ret' to 'res' in this patch.\n\nApart from that this all looks sensible to me, it is nice to see the \nnumber of leaks going down.\n\nThanks\n\nPhillip\n\n"},{"id":"496198","messageId":"Zl6390B6kUu5TlBM@framework","threadId":"61578","inReplyTo":"758f2321-dc63-4209-8b54-99b74b6bb897@gmail.com","subject":"Re: [PATCH 20/29] sequencer: fix leaking string buffer in `commit_staged_changes()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T06:45:11Z","receivedAt":"2024-06-04T06:45:17Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Mon, Jun 03, 2024 at 02:14:20PM +0100, Phillip Wood wrote:\n> Hi Patrick\n> \n> On 03/06/2024 10:48, Patrick Steinhardt wrote:\n> > @@ -5259,12 +5277,13 @@ static int commit_staged_changes(struct repository *r,\n> >   \t\t\t\t}\n> >   \t\t\tunuse_commit_buffer:\n> >   \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n> > -\t\t\t\tif (res)\n> > -\t\t\t\t\treturn res;\n> > +\t\t\t\tif (res) {\n> > +\t\t\t\t\tret = res;\n> > +\t\t\t\t\tgoto out;\n> > +\t\t\t\t}\n> \n> Having 'ret' and 'res' in this block is a bit confusing - we could delete\n> the declaration for 'res' and  either replace its use with 'ret', or rename\n> 'ret' to 'res' in this patch.\n\nYeah, let's just drop the local `res` variable here and use `ret`\ninstead.\n\n> Apart from that this all looks sensible to me, it is nice to see the number\n> of leaks going down.\n> \n> Thanks\n\nThanks!\n\nPatrick\n"},{"id":"496203","messageId":"Zl6_6Z6Bu-zgOmPs@framework","threadId":"61578","inReplyTo":"Zl6390B6kUu5TlBM@framework","subject":"Re: [PATCH 20/29] sequencer: fix leaking string buffer in `commit_staged_changes()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-04T07:19:05Z","receivedAt":"2024-06-04T07:19:11Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Tue, Jun 04, 2024 at 08:45:11AM +0200, Patrick Steinhardt wrote:\n> On Mon, Jun 03, 2024 at 02:14:20PM +0100, Phillip Wood wrote:\n> > Hi Patrick\n> > \n> > On 03/06/2024 10:48, Patrick Steinhardt wrote:\n> > > @@ -5259,12 +5277,13 @@ static int commit_staged_changes(struct repository *r,\n> > >   \t\t\t\t}\n> > >   \t\t\tunuse_commit_buffer:\n> > >   \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n> > > -\t\t\t\tif (res)\n> > > -\t\t\t\t\treturn res;\n> > > +\t\t\t\tif (res) {\n> > > +\t\t\t\t\tret = res;\n> > > +\t\t\t\t\tgoto out;\n> > > +\t\t\t\t}\n> > \n> > Having 'ret' and 'res' in this block is a bit confusing - we could delete\n> > the declaration for 'res' and  either replace its use with 'ret', or rename\n> > 'ret' to 'res' in this patch.\n> \n> Yeah, let's just drop the local `res` variable here and use `ret`\n> instead.\n\nFor the record, below is the diff to address this. I'll refrain from\nsending out this whole patch series again for now to only address this\none issue, but will include it if there are more things that need to be\nhandled.\n\nPatrick\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 9e90084692..cc57a30883 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5253,7 +5253,6 @@ static int commit_staged_changes(struct repository *r,\n \t\t\t\t * We need to update the squash message to skip\n \t\t\t\t * the latest commit message.\n \t\t\t\t */\n-\t\t\t\tint res = 0;\n \t\t\t\tstruct commit *commit;\n \t\t\t\tconst char *msg;\n \t\t\t\tconst char *path = rebase_path_squash_msg();\n@@ -5266,22 +5265,22 @@ static int commit_staged_changes(struct repository *r,\n \n \t\t\t\tp = repo_logmsg_reencode(r, commit, NULL, encoding);\n \t\t\t\tif (!p)  {\n-\t\t\t\t\tres = error(_(\"could not parse commit %s\"),\n+\t\t\t\t\tret = error(_(\"could not parse commit %s\"),\n \t\t\t\t\t\t    oid_to_hex(&commit->object.oid));\n \t\t\t\t\tgoto unuse_commit_buffer;\n \t\t\t\t}\n \t\t\t\tfind_commit_subject(p, &msg);\n \t\t\t\tif (write_message(msg, strlen(msg), path, 0)) {\n-\t\t\t\t\tres = error(_(\"could not write file: \"\n+\t\t\t\t\tret = error(_(\"could not write file: \"\n \t\t\t\t\t\t       \"'%s'\"), path);\n \t\t\t\t\tgoto unuse_commit_buffer;\n \t\t\t\t}\n+\n+\t\t\t\tret = 0;\n \t\t\tunuse_commit_buffer:\n \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n-\t\t\t\tif (res) {\n-\t\t\t\t\tret = res;\n+\t\t\t\tif (ret)\n \t\t\t\t\tgoto out;\n-\t\t\t\t}\n \t\t\t}\n \t\t}\n \n"},{"id":"496279","messageId":"396e6893-80d4-48f5-b43f-a30684a64bac@gmail.com","threadId":"61578","inReplyTo":"Zl6_6Z6Bu-zgOmPs@framework","subject":"Re: [PATCH 20/29] sequencer: fix leaking string buffer in `commit_staged_changes()`","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-06-04T13:58:17Z","receivedAt":"2024-06-04T13:58:19Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 04/06/2024 08:19, Patrick Steinhardt wrote:\n> On Tue, Jun 04, 2024 at 08:45:11AM +0200, Patrick Steinhardt wrote:\n>> On Mon, Jun 03, 2024 at 02:14:20PM +0100, Phillip Wood wrote:\n>>> Hi Patrick\n>>>\n>>> On 03/06/2024 10:48, Patrick Steinhardt wrote:\n>>>> @@ -5259,12 +5277,13 @@ static int commit_staged_changes(struct repository *r,\n>>>>    \t\t\t\t}\n>>>>    \t\t\tunuse_commit_buffer:\n>>>>    \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n>>>> -\t\t\t\tif (res)\n>>>> -\t\t\t\t\treturn res;\n>>>> +\t\t\t\tif (res) {\n>>>> +\t\t\t\t\tret = res;\n>>>> +\t\t\t\t\tgoto out;\n>>>> +\t\t\t\t}\n>>>\n>>> Having 'ret' and 'res' in this block is a bit confusing - we could delete\n>>> the declaration for 'res' and  either replace its use with 'ret', or rename\n>>> 'ret' to 'res' in this patch.\n>>\n>> Yeah, let's just drop the local `res` variable here and use `ret`\n>> instead.\n> \n> For the record, below is the diff to address this. I'll refrain from\n> sending out this whole patch series again for now to only address this\n> one issue, but will include it if there are more things that need to be\n> handled.\n> \n> Patrick\n\nThis and all the other sequencer changes in this series look good to me\n\nThanks\n\nPhillip\n\n> diff --git a/sequencer.c b/sequencer.c\n> index 9e90084692..cc57a30883 100644\n> --- a/sequencer.c\n> +++ b/sequencer.c\n> @@ -5253,7 +5253,6 @@ static int commit_staged_changes(struct repository *r,\n>   \t\t\t\t * We need to update the squash message to skip\n>   \t\t\t\t * the latest commit message.\n>   \t\t\t\t */\n> -\t\t\t\tint res = 0;\n>   \t\t\t\tstruct commit *commit;\n>   \t\t\t\tconst char *msg;\n>   \t\t\t\tconst char *path = rebase_path_squash_msg();\n> @@ -5266,22 +5265,22 @@ static int commit_staged_changes(struct repository *r,\n>   \n>   \t\t\t\tp = repo_logmsg_reencode(r, commit, NULL, encoding);\n>   \t\t\t\tif (!p)  {\n> -\t\t\t\t\tres = error(_(\"could not parse commit %s\"),\n> +\t\t\t\t\tret = error(_(\"could not parse commit %s\"),\n>   \t\t\t\t\t\t    oid_to_hex(&commit->object.oid));\n>   \t\t\t\t\tgoto unuse_commit_buffer;\n>   \t\t\t\t}\n>   \t\t\t\tfind_commit_subject(p, &msg);\n>   \t\t\t\tif (write_message(msg, strlen(msg), path, 0)) {\n> -\t\t\t\t\tres = error(_(\"could not write file: \"\n> +\t\t\t\t\tret = error(_(\"could not write file: \"\n>   \t\t\t\t\t\t       \"'%s'\"), path);\n>   \t\t\t\t\tgoto unuse_commit_buffer;\n>   \t\t\t\t}\n> +\n> +\t\t\t\tret = 0;\n>   \t\t\tunuse_commit_buffer:\n>   \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n> -\t\t\t\tif (res) {\n> -\t\t\t\t\tret = res;\n> +\t\t\t\tif (ret)\n>   \t\t\t\t\tgoto out;\n> -\t\t\t\t}\n>   \t\t\t}\n>   \t\t}\n>   \n"},{"id":"496478","messageId":"CAOLa=ZQj1UrNO+nOxhp2kaO+F1hHDE14FMYi74Eg8sMAgXz8OQ@mail.gmail.com","threadId":"61578","inReplyTo":"ecabbb74e19ea773fd23719bd7fd4937b29679e5.1717402439.git.ps@pks.im","subject":"Re: [PATCH 02/29] parse-options: fix leaks for users of OPT_FILENAME","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-06-06T10:00:40Z","receivedAt":"2024-06-06T10:00:42Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n[snip]\n\n> diff --git a/builtin/archive.c b/builtin/archive.c\n> index 15ee1ec7bb..f29c0ef6ad 100644\n> --- a/builtin/archive.c\n> +++ b/builtin/archive.c\n> @@ -92,6 +92,7 @@ int cmd_archive(int argc, const char **argv, const char *prefix)\n>  \t\t\tN_(\"path to the remote git-upload-archive command\")),\n>  \t\tOPT_END()\n>  \t};\n> +\tint ret;\n>\n>  \targc = parse_options(argc, argv, prefix, local_opts, NULL,\n>  \t\t\t     PARSE_OPT_KEEP_ALL);\n> @@ -106,6 +107,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix)\n>\n>  \tsetvbuf(stderr, NULL, _IOLBF, BUFSIZ);\n>\n> -\tUNLEAK(output);\n> -\treturn write_archive(argc, argv, prefix, the_repository, output, 0);\n> +\tret = write_archive(argc, argv, prefix, the_repository, output, 0);\n> +\n> +\tfree(output);\n> +\treturn ret;\n\nSince output is passed to `write_archive`, we capture the return and\nfree output. Makes sense.\n\n[snip]\n\n> diff --git a/builtin/log.c b/builtin/log.c\n> index 78a247d8a9..4e4b645a21 100644\n> --- a/builtin/log.c\n> +++ b/builtin/log.c\n> @@ -2021,7 +2021,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n>  \tconst char *rfc = NULL;\n>  \tint creation_factor = -1;\n>  \tconst char *signature = git_version_string;\n> -\tconst char *signature_file_arg = NULL;\n> +\tchar *signature_file_arg = NULL;\n>  \tstruct keep_callback_data keep_callback_data = {\n>  \t\t.cfg = &cfg,\n>  \t\t.revs = &rev,\n> @@ -2559,6 +2559,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n>  \tstrbuf_release(&rdiff1);\n>  \tstrbuf_release(&rdiff2);\n>  \tstrbuf_release(&rdiff_title);\n> +\tfree(description_file);\n> +\tfree(signature_file_arg);\n>  \tfree(to_free);\n>  \tfree(rev.message_id);\n>  \tif (rev.ref_message_ids)\n>\n\nI see `char *description_file = NULL` already and it is also used\n`OPT_FILENAME`. So all good here.\n\nThe rest of the patch looks good too. This was commit was quite easy to\nready. Appreciate the neat breakdown.\n"},{"id":"496479","messageId":"CAOLa=ZQqGoVtG2uHRxf284H3JhYq09Dxy12Fj6UmiGyAjXwe=Q@mail.gmail.com","threadId":"61578","inReplyTo":"17a8f532752e005601b1cff5690a4dae16096b8b.1717402439.git.ps@pks.im","subject":"Re: [PATCH 03/29] notes-utils: free note trees when releasing copied notes","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-06-06T10:02:08Z","receivedAt":"2024-06-06T10:02:09Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> While we clear most of the members of `struct notet_rewrite_cfg` in\n> `finish_copy_notes_for_rewrite()`, we do not clear the notes tree. Fix\n> this to plug this memory leak.\n>\n\nNit: s/notet_rewrite_cfg/notes_rewrite_cfg\n\nThe commit itself looks good.\n"},{"id":"496481","messageId":"CAOLa=ZT3XvdpheQCV9VQinnppitU3ghqvB_Qs-+zm0OyrAOrSQ@mail.gmail.com","threadId":"61578","inReplyTo":"0052d60ee889981f2ba92c9781e1659e961eace4.1717402439.git.ps@pks.im","subject":"Re: [PATCH 06/29] merge-recursive: fix leaging rename conflict info","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-06-06T10:07:45Z","receivedAt":"2024-06-06T10:07:47Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\nIn the subject:\n\ns/leaging/leaking\n\n> When computing rename conflicts in our recursive merge algorithm we set\n> up `struct rename_conflict_info`s to track that information. We never\n> free those data structures though and thus leak memory.\n>\n> We need to be a bit more careful here though because the same rename\n> conflict info can be assigned to multiple structures. Accomodate for\n\nNit: s/Accomodate/Accommodate\n\n[snip]\n"},{"id":"496512","messageId":"CAOLa=ZQ5qN9+GGp2Cmks+J2dCbZpNvd0ZEGTWejedUCtzXF9Ew@mail.gmail.com","threadId":"61578","inReplyTo":"930de119116355f0b6df9e7e1d5d0fe38d93755a.1717402439.git.ps@pks.im","subject":"Re: [PATCH 13/29] merge-recursive: fix memory leak when finalizing merge","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-06-06T10:50:32Z","receivedAt":"2024-06-06T10:50:34Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> We do not free some members of `struct merge_options`' private data.\n\nNit: s/`struct merge_options`'/`struct merge_options`'s/\n\n[snip]\n"},{"id":"496523","messageId":"CAOLa=ZS39hobSHXzyq=vAL9KqBkr9PaSZyqyELvFxvAA=uSofw@mail.gmail.com","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"Re: [PATCH 00/29] Memory leak fixes (pt.2)","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-06-06T14:33:34Z","receivedAt":"2024-06-06T14:33:37Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> this is another random assortment of memory leak fixes for Git. With\n> these changes, another 77 test suites start to pass with the leak\n> sanitizer enabled. With that, we're down to ~270 test suites that do not\n> yet pass with the sanitizer enabled. My goal is that we can reduce this\n> number to 0 this year -- any help here would be appreciated.\n>\n\nI was thinking of picking up some here, how do I avoid not clashing in\nwith the work that you're doing?\n\n> While most of the commits are trivial, I realize that the overall series\n> is quite large. If you think that this is too big, please feel free to\n> speak up up and I'll split this and future series into batches of at\n> most 20 patches or less, if you think that's still too many.\n>\n> The series is built on top of 9eaef5822c (Sync with 'maint', 2024-05-31)\n> with ps/leakfixes at 164937678c (Merge remote-tracking branch\n> 'junio/ps/leakfixes' into HEAD, 2024-06-03) merged into it.\n>\n> Thanks!\n>\n\nI only have some small typo nits apart from that this was a nice read. Thanks!\n"},{"id":"496536","messageId":"4ba1343d-1467-46a0-85b0-b249e1ddedf7@gmail.com","threadId":"61578","inReplyTo":"CAOLa=ZQ5qN9+GGp2Cmks+J2dCbZpNvd0ZEGTWejedUCtzXF9Ew@mail.gmail.com","subject":"Re: [PATCH 13/29] merge-recursive: fix memory leak when finalizing merge","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-06-06T15:52:11Z","receivedAt":"2024-06-06T15:52:16Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"On 06/06/2024 11:50, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n>> We do not free some members of `struct merge_options`' private data.\n> \n> Nit: s/`struct merge_options`'/`struct merge_options`'s/\n\nEither is correct, it is a matter of style whether one adds the trailing \n\"s\".\n\nBest Wishes\n\nPhillip\n"},{"id":"496596","messageId":"ZmKHZaQHHIDpBRMQ@tanuki","threadId":"61578","inReplyTo":"CAOLa=ZS39hobSHXzyq=vAL9KqBkr9PaSZyqyELvFxvAA=uSofw@mail.gmail.com","subject":"Re: [PATCH 00/29] Memory leak fixes (pt.2)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-07T04:07:01Z","receivedAt":"2024-06-07T04:07:08Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"On Thu, Jun 06, 2024 at 07:33:34AM -0700, Karthik Nayak wrote:\n> Patrick Steinhardt <ps@pks.im> writes:\n> \n> > Hi,\n> >\n> > this is another random assortment of memory leak fixes for Git. With\n> > these changes, another 77 test suites start to pass with the leak\n> > sanitizer enabled. With that, we're down to ~270 test suites that do not\n> > yet pass with the sanitizer enabled. My goal is that we can reduce this\n> > number to 0 this year -- any help here would be appreciated.\n> >\n> \n> I was thinking of picking up some here, how do I avoid not clashing in\n> with the work that you're doing?\n\nYou mean work that hasn't yet been posted to the mailing list? I don't\nthink there is a perfect answer here. I guess the next-best thing is to\nnot keep your patch series sitting for an extended amount of time, but\nlimit the time you are working on it and then send it out early such\nthat others can see it and ideally refrain from plugging the same leaks.\n\n> > While most of the commits are trivial, I realize that the overall series\n> > is quite large. If you think that this is too big, please feel free to\n> > speak up up and I'll split this and future series into batches of at\n> > most 20 patches or less, if you think that's still too many.\n> >\n> > The series is built on top of 9eaef5822c (Sync with 'maint', 2024-05-31)\n> > with ps/leakfixes at 164937678c (Merge remote-tracking branch\n> > 'junio/ps/leakfixes' into HEAD, 2024-06-03) merged into it.\n> >\n> > Thanks!\n> >\n> \n> I only have some small typo nits apart from that this was a nice read. Thanks!\n\nThanks!\n\nPatrick\n"},{"id":"496860","messageId":"cover.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1717402439.git.ps@pks.im","subject":"[PATCH v2 00/29] Memory leak fixes (pt.2)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:13Z","receivedAt":"2024-06-11T09:19:19Z","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 fixes various memory\nleaks in our codebase.\n\nChanges compared to v1:\n\n  - Fix various typos in commit messages.\n\n  - Refactor patch 20 such that we do not have both `ret` and `res` as\n    local variables, which was confusing.\n\nThanks!\n\nPatrick\n\nPatrick Steinhardt (29):\n  revision: fix memory leak when reversing revisions\n  parse-options: fix leaks for users of OPT_FILENAME\n  notes-utils: free note trees when releasing copied notes\n  bundle: plug leaks in `create_bundle()`\n  biultin/rev-parse: fix memory leaks in `--parseopt` mode\n  merge-recursive: fix leaking rename conflict info\n  revision: fix leaking display notes\n  notes: fix memory leak when pruning notes\n  builtin/rev-list: fix leaking bitmap index when calculating disk usage\n  object-name: free leaking object contexts\n  builtin/difftool: plug memory leaks in `run_dir_diff()`\n  builtin/merge-recursive: fix leaking object ID bases\n  merge-recursive: fix memory leak when finalizing merge\n  builtin/log: fix leaking commit list in git-cherry(1)\n  revision: free diff options\n  builtin/stash: fix leak in `show_stash()`\n  rerere: fix various trivial leaks\n  config: fix leaking \"core.notesref\" variable\n  commit: fix leaking parents when calling `commit_tree_extended()`\n  sequencer: fix leaking string buffer in `commit_staged_changes()`\n  apply: fix leaking string in `match_fragment()`\n  builtin/clone: plug leaking HEAD ref in `wanted_peer_refs()`\n  sequencer: fix memory leaks in `make_script_with_merges()`\n  builtin/merge: fix leaking `struct cmdnames` in `get_strategy()`\n  merge: fix leaking merge bases\n  line-range: plug leaking find functions\n  blame: fix leaking data for blame scoreboards\n  builtin/blame: fix leaking prefixed paths\n  builtin/blame: fix leaking ignore revs files\n\n apply.c                                   |  88 ++++++++++------\n apply.h                                   |   2 +-\n blame.c                                   |   4 +\n builtin/am.c                              |   7 +-\n builtin/archive.c                         |   7 +-\n builtin/blame.c                           |   8 +-\n builtin/cat-file.c                        |  17 ++--\n builtin/clone.c                           |   3 +-\n builtin/commit-tree.c                     |  11 +-\n builtin/commit.c                          |  10 +-\n builtin/difftool.c                        |   3 +\n builtin/fmt-merge-msg.c                   |   4 +-\n builtin/grep.c                            |   4 +-\n builtin/log.c                             |  16 +--\n builtin/ls-tree.c                         |   3 +-\n builtin/merge-recursive.c                 |   6 +-\n builtin/merge-tree.c                      |   1 +\n builtin/merge.c                           |  18 +++-\n builtin/multi-pack-index.c                |  13 ++-\n builtin/replay.c                          |  14 ++-\n builtin/rev-list.c                        |   2 +\n builtin/rev-parse.c                       |  55 +++++-----\n builtin/shortlog.c                        |   5 +-\n builtin/sparse-checkout.c                 |   1 +\n builtin/stash.c                           |  23 +++--\n bundle.c                                  |  29 ++++--\n commit.c                                  |  28 +++---\n commit.h                                  |  12 +--\n config.c                                  |   1 +\n diff-lib.c                                |   2 +\n diff.c                                    |   8 +-\n help.c                                    |  12 +--\n help.h                                    |   2 +\n line-range.c                              |   2 +\n list-objects-filter.c                     |   2 +\n log-tree.c                                |   1 +\n merge-ort-wrappers.c                      |   2 +-\n merge-ort-wrappers.h                      |   2 +-\n merge-ort.c                               |  12 ++-\n merge-ort.h                               |   2 +-\n merge-recursive.c                         |  68 ++++++++-----\n merge-recursive.h                         |   4 +-\n notes-merge.c                             |   1 +\n notes-utils.c                             |   9 +-\n notes-utils.h                             |   2 +-\n notes.c                                   |  21 ++--\n notes.h                                   |   5 +\n object-name.c                             |  40 ++++++--\n object-name.h                             |   2 +\n rerere.c                                  |   3 +\n revision.c                                |  59 +++++++----\n sequencer.c                               | 116 +++++++++++++++-------\n t/helper/test-parse-options.c             |   1 +\n t/t1004-read-tree-m-u-wf.sh               |   1 +\n t/t1015-read-index-unmerged.sh            |   2 +\n t/t1021-rerere-in-workdir.sh              |   1 +\n t/t1512-rev-parse-disambiguation.sh       |   1 +\n t/t2500-untracked-overwriting.sh          |   1 +\n t/t3301-notes.sh                          |   1 +\n t/t3306-notes-prune.sh                    |   1 +\n t/t3308-notes-merge.sh                    |   1 +\n t/t3309-notes-merge-auto-resolve.sh       |   1 +\n t/t3400-rebase.sh                         |   1 +\n t/t3401-rebase-and-am-rename.sh           |   1 +\n t/t3403-rebase-skip.sh                    |   1 +\n t/t3406-rebase-message.sh                 |   1 +\n t/t3407-rebase-abort.sh                   |   1 +\n t/t3417-rebase-whitespace-fix.sh          |   1 +\n t/t3418-rebase-continue.sh                |   1 +\n t/t3420-rebase-autostash.sh               |   1 +\n t/t3421-rebase-topology-linear.sh         |   2 +\n t/t3424-rebase-empty.sh                   |   1 +\n t/t3428-rebase-signoff.sh                 |   1 +\n t/t3430-rebase-merges.sh                  |   1 +\n t/t3434-rebase-i18n.sh                    |   1 +\n t/t3500-cherry.sh                         |   1 +\n t/t3504-cherry-pick-rerere.sh             |   1 +\n t/t3505-cherry-pick-empty.sh              |   1 +\n t/t3508-cherry-pick-many-commits.sh       |   1 +\n t/t3509-cherry-pick-merge-df.sh           |   1 +\n t/t3907-stash-show-config.sh              |   1 +\n t/t4061-diff-indent.sh                    |   1 +\n t/t4131-apply-fake-ancestor.sh            |   1 +\n t/t4151-am-abort.sh                       |   1 +\n t/t4153-am-resume-override-opts.sh        |   1 +\n t/t4208-log-magic-pathspec.sh             |   1 +\n t/t4253-am-keep-cr-dos.sh                 |   1 +\n t/t4255-am-submodule.sh                   |   1 +\n t/t5150-request-pull.sh                   |   1 +\n t/t5300-pack-object.sh                    |   4 +-\n t/t5305-include-tag.sh                    |   1 +\n t/t5407-post-rewrite-hook.sh              |   1 +\n t/t5605-clone-local.sh                    |   1 +\n t/t5607-clone-bundle.sh                   |   1 +\n t/t5612-clone-refspec.sh                  |   1 +\n t/t6000-rev-list-misc.sh                  |   1 +\n t/t6001-rev-list-graft.sh                 |   1 +\n t/t6013-rev-list-reverse-parents.sh       |   1 +\n t/t6017-rev-list-stdin.sh                 |   1 +\n t/t6020-bundle-misc.sh                    |   1 +\n t/t6115-rev-list-du.sh                    |   2 +\n t/t6130-pathspec-noglob.sh                |   2 +\n t/t6402-merge-rename.sh                   |   1 +\n t/t6427-diff3-conflict-markers.sh         |   1 +\n t/t6430-merge-recursive.sh                |   1 +\n t/t6432-merge-recursive-space-options.sh  |   1 +\n t/t6434-merge-recursive-rename-options.sh |   1 +\n t/t6436-merge-overwrite.sh                |   1 +\n t/t7006-pager.sh                          |   1 +\n t/t7010-setup.sh                          |   1 +\n t/t7012-skip-worktree-writing.sh          |   1 +\n t/t7201-co.sh                             |   1 +\n t/t7501-commit-basic-functionality.sh     |   1 +\n t/t7505-prepare-commit-msg-hook.sh        |   1 +\n t/t7512-status-help.sh                    |   1 +\n t/t7600-merge.sh                          |   1 +\n t/t7606-merge-custom.sh                   |   1 +\n t/t7611-merge-abort.sh                    |   1 +\n t/t8002-blame.sh                          |   1 +\n t/t8003-blame-corner-cases.sh             |   1 +\n t/t8004-blame-with-conflicts.sh           |   1 +\n t/t8006-blame-textconv.sh                 |   2 +\n t/t8008-blame-formats.sh                  |   2 +\n t/t8009-blame-vs-topicbranches.sh         |   2 +\n t/t8011-blame-split-file.sh               |   2 +\n t/t8012-blame-colors.sh                   |   1 +\n t/t8013-blame-ignore-revs.sh              |   2 +\n t/t8014-blame-ignore-fuzzy.sh             |   2 +\n t/t9500-gitweb-standalone-no-errors.sh    |   1 +\n t/t9502-gitweb-standalone-parse-output.sh |   1 +\n 130 files changed, 591 insertions(+), 272 deletions(-)\n\nRange-diff against v1:\n 1:  a88a6fa266 =  1:  f602bb5444 revision: fix memory leak when reversing revisions\n 2:  ecabbb74e1 =  2:  84f2f56b71 parse-options: fix leaks for users of OPT_FILENAME\n 3:  17a8f53275 !  3:  c5f49bf930 notes-utils: free note trees when releasing copied notes\n    @@ Metadata\n      ## Commit message ##\n         notes-utils: free note trees when releasing copied notes\n     \n    -    While we clear most of the members of `struct notet_rewrite_cfg` in\n    +    While we clear most of the members of `struct notes_rewrite_cfg` in\n         `finish_copy_notes_for_rewrite()`, we do not clear the notes tree. Fix\n         this to plug this memory leak.\n     \n 4:  0c8c97cded =  4:  5a1eae2cc9 bundle: plug leaks in `create_bundle()`\n 5:  620814fb99 =  5:  91eefcf64a biultin/rev-parse: fix memory leaks in `--parseopt` mode\n 6:  0052d60ee8 !  6:  f16c29a20a merge-recursive: fix leaging rename conflict info\n    @@ Metadata\n     Author: Patrick Steinhardt <ps@pks.im>\n     \n      ## Commit message ##\n    -    merge-recursive: fix leaging rename conflict info\n    +    merge-recursive: fix leaking rename conflict info\n     \n         When computing rename conflicts in our recursive merge algorithm we set\n         up `struct rename_conflict_info`s to track that information. We never\n         free those data structures though and thus leak memory.\n     \n         We need to be a bit more careful here though because the same rename\n    -    conflict info can be assigned to multiple structures. Accomodate for\n    +    conflict info can be assigned to multiple structures. Accommodate for\n         this by introducing a `rename_conflict_info_owned` bit that we can use\n         to steer whether or not the rename conflict info shall be free'd.\n     \n 7:  8c55370b8e =  7:  b9c3a34ae1 revision: fix leaking display notes\n 8:  17671dc9cf =  8:  3ea5e5c6bd notes: fix memory leak when pruning notes\n 9:  5e01c39e35 =  9:  a0242359b7 builtin/rev-list: fix leaking bitmap index when calculating disk usage\n10:  2ac24bf942 = 10:  bdb7389328 object-name: free leaking object contexts\n11:  8349f6acbd = 11:  e6ff9a3f1c builtin/difftool: plug memory leaks in `run_dir_diff()`\n12:  5d9fb78e63 = 12:  fb30a254ab builtin/merge-recursive: fix leaking object ID bases\n13:  930de11911 = 13:  b6540e4aff merge-recursive: fix memory leak when finalizing merge\n14:  3e3243f884 = 14:  7cb4f75178 builtin/log: fix leaking commit list in git-cherry(1)\n15:  a754800fd1 = 15:  99f4f3d341 revision: free diff options\n16:  8bde010d1d = 16:  707d798f78 builtin/stash: fix leak in `show_stash()`\n17:  cadeb09b95 = 17:  7d73c50872 rerere: fix various trivial leaks\n18:  cff6303ce1 = 18:  1700ec8d21 config: fix leaking \"core.notesref\" variable\n19:  8ada8864d8 = 19:  c1a1eb34ab commit: fix leaking parents when calling `commit_tree_extended()`\n20:  48bcd0ac80 ! 20:  144eb23617 sequencer: fix leaking string buffer in `commit_staged_changes()`\n    @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n      \t\t\t/*\n      \t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n     @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n    + \t\t\t\t * We need to update the squash message to skip\n    + \t\t\t\t * the latest commit message.\n    + \t\t\t\t */\n    +-\t\t\t\tint res = 0;\n    + \t\t\t\tstruct commit *commit;\n    + \t\t\t\tconst char *msg;\n      \t\t\t\tconst char *path = rebase_path_squash_msg();\n      \t\t\t\tconst char *encoding = get_commit_output_encoding();\n      \n    @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n      \n      \t\t\t\tp = repo_logmsg_reencode(r, commit, NULL, encoding);\n      \t\t\t\tif (!p)  {\n    -@@ sequencer.c: static int commit_staged_changes(struct repository *r,\n    +-\t\t\t\t\tres = error(_(\"could not parse commit %s\"),\n    ++\t\t\t\t\tret = error(_(\"could not parse commit %s\"),\n    + \t\t\t\t\t\t    oid_to_hex(&commit->object.oid));\n    + \t\t\t\t\tgoto unuse_commit_buffer;\n    + \t\t\t\t}\n    + \t\t\t\tfind_commit_subject(p, &msg);\n    + \t\t\t\tif (write_message(msg, strlen(msg), path, 0)) {\n    +-\t\t\t\t\tres = error(_(\"could not write file: \"\n    ++\t\t\t\t\tret = error(_(\"could not write file: \"\n    + \t\t\t\t\t\t       \"'%s'\"), path);\n    + \t\t\t\t\tgoto unuse_commit_buffer;\n      \t\t\t\t}\n    ++\n    ++\t\t\t\tret = 0;\n    ++\n      \t\t\tunuse_commit_buffer:\n      \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n     -\t\t\t\tif (res)\n     -\t\t\t\t\treturn res;\n    -+\t\t\t\tif (res) {\n    -+\t\t\t\t\tret = res;\n    ++\t\t\t\tif (ret)\n     +\t\t\t\t\tgoto out;\n    -+\t\t\t\t}\n      \t\t\t}\n      \t\t}\n      \n21:  ebed18717f = 21:  8e1cf8a18b apply: fix leaking string in `match_fragment()`\n22:  4f4ae5ea96 = 22:  8090891bd0 builtin/clone: plug leaking HEAD ref in `wanted_peer_refs()`\n23:  713531c6f1 = 23:  1cdd988c45 sequencer: fix memory leaks in `make_script_with_merges()`\n24:  eeef280dd5 = 24:  be9bccc853 builtin/merge: fix leaking `struct cmdnames` in `get_strategy()`\n25:  5441f1e2ef = 25:  6f6456494c merge: fix leaking merge bases\n26:  ba9c2b4ea7 = 26:  67f8637dc6 line-range: plug leaking find functions\n27:  5f2b0adc56 = 27:  bd6db56a3f blame: fix leaking data for blame scoreboards\n28:  7ee8734437 = 28:  c1291025fa builtin/blame: fix leaking prefixed paths\n29:  5b5fdfc6cb = 29:  5d7007e724 builtin/blame: fix leaking ignore revs files\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496861","messageId":"f602bb5444b49058e3782e013fcec61ea486a16a.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 01/29] revision: fix memory leak when reversing revisions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:17Z","receivedAt":"2024-06-11T09:19:22Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When reversing revisions in a rev walk, `get_revision()` will allocate a\nnew commit list and assign it to `revs->commits`. It does not free the\nold list though, which makes it leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n revision.c                          | 1 +\n t/t3508-cherry-pick-many-commits.sh | 1 +\n 2 files changed, 2 insertions(+)\n\ndiff --git a/revision.c b/revision.c\nindex 7ddf0f151a..af95502d92 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -4430,6 +4430,7 @@ struct commit *get_revision(struct rev_info *revs)\n \t\treversed = NULL;\n \t\twhile ((c = get_revision_internal(revs)))\n \t\t\tcommit_list_insert(c, &reversed);\n+\t\tfree_commit_list(revs->commits);\n \t\trevs->commits = reversed;\n \t\trevs->reverse = 0;\n \t\trevs->reverse_output_stage = 1;\ndiff --git a/t/t3508-cherry-pick-many-commits.sh b/t/t3508-cherry-pick-many-commits.sh\nindex 2d53ce754c..afa7727a4a 100755\n--- a/t/t3508-cherry-pick-many-commits.sh\n+++ b/t/t3508-cherry-pick-many-commits.sh\n@@ -5,6 +5,7 @@ test_description='test cherry-picking many commits'\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_head_differs_from() {\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496862","messageId":"84f2f56b717b2d081ca76532d821e474412dbc05.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 02/29] parse-options: fix leaks for users of OPT_FILENAME","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:22Z","receivedAt":"2024-06-11T09:19:26Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The `OPT_FILENAME()` option will, if set, put an allocated string into\nthe user-provided variable. Consequently, that variable thus needs to be\nfree'd by the caller of `parse_options()`. Some callsites don't though\nand thus leak memory. Fix those.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n apply.c                             |  1 +\n apply.h                             |  2 +-\n builtin/archive.c                   |  7 +++++--\n builtin/commit.c                    |  7 +++++--\n builtin/fmt-merge-msg.c             |  4 +++-\n builtin/log.c                       |  4 +++-\n builtin/multi-pack-index.c          | 13 +++++++++----\n builtin/sparse-checkout.c           |  1 +\n t/helper/test-parse-options.c       |  1 +\n t/t1512-rev-parse-disambiguation.sh |  1 +\n t/t2500-untracked-overwriting.sh    |  1 +\n t/t3406-rebase-message.sh           |  1 +\n t/t3407-rebase-abort.sh             |  1 +\n t/t3428-rebase-signoff.sh           |  1 +\n t/t4131-apply-fake-ancestor.sh      |  1 +\n t/t4151-am-abort.sh                 |  1 +\n t/t4253-am-keep-cr-dos.sh           |  1 +\n t/t4255-am-submodule.sh             |  1 +\n t/t5407-post-rewrite-hook.sh        |  1 +\n t/t6427-diff3-conflict-markers.sh   |  1 +\n t/t7512-status-help.sh              |  1 +\n 21 files changed, 41 insertions(+), 11 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 901b67e625..d8d26a48f1 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -135,6 +135,7 @@ void clear_apply_state(struct apply_state *state)\n \tstrset_clear(&state->removed_symlinks);\n \tstrset_clear(&state->kept_symlinks);\n \tstrbuf_release(&state->root);\n+\tFREE_AND_NULL(state->fake_ancestor);\n \n \t/* &state->fn_table is cleared at the end of apply_patch() */\n }\ndiff --git a/apply.h b/apply.h\nindex 7cd38b1443..36d7c3f70b 100644\n--- a/apply.h\n+++ b/apply.h\n@@ -59,7 +59,7 @@ struct apply_state {\n \tstruct repository *repo;\n \tconst char *index_file;\n \tenum apply_verbosity apply_verbosity;\n-\tconst char *fake_ancestor;\n+\tchar *fake_ancestor;\n \tconst char *patch_input_file;\n \tint line_termination;\n \tstruct strbuf root;\ndiff --git a/builtin/archive.c b/builtin/archive.c\nindex 15ee1ec7bb..f29c0ef6ad 100644\n--- a/builtin/archive.c\n+++ b/builtin/archive.c\n@@ -92,6 +92,7 @@ int cmd_archive(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"path to the remote git-upload-archive command\")),\n \t\tOPT_END()\n \t};\n+\tint ret;\n \n \targc = parse_options(argc, argv, prefix, local_opts, NULL,\n \t\t\t     PARSE_OPT_KEEP_ALL);\n@@ -106,6 +107,8 @@ int cmd_archive(int argc, const char **argv, const char *prefix)\n \n \tsetvbuf(stderr, NULL, _IOLBF, BUFSIZ);\n \n-\tUNLEAK(output);\n-\treturn write_archive(argc, argv, prefix, the_repository, output, 0);\n+\tret = write_archive(argc, argv, prefix, the_repository, output, 0);\n+\n+\tfree(output);\n+\treturn ret;\n }\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex f53e7e86ff..dcaf4efa03 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -106,7 +106,8 @@ static enum {\n \tCOMMIT_PARTIAL\n } commit_style;\n \n-static const char *logfile, *force_author;\n+static const char *force_author;\n+static char *logfile;\n static char *template_file;\n /*\n  * The _message variables are commit names from which to take\n@@ -1309,7 +1310,7 @@ static int parse_and_validate_options(int argc, const char *argv[],\n \t\t\t\t  !!use_message, \"-C\",\n \t\t\t\t  !!logfile, \"-F\");\n \tif (use_message || edit_message || logfile ||fixup_message || have_option_m)\n-\t\ttemplate_file = NULL;\n+\t\tFREE_AND_NULL(template_file);\n \tif (edit_message)\n \t\tuse_message = edit_message;\n \tif (amend && !use_message && !fixup_message)\n@@ -1892,5 +1893,7 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&author_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\n+\tfree(logfile);\n+\tfree(template_file);\n \treturn ret;\n }\ndiff --git a/builtin/fmt-merge-msg.c b/builtin/fmt-merge-msg.c\nindex 0f9855b680..957786d1b3 100644\n--- a/builtin/fmt-merge-msg.c\n+++ b/builtin/fmt-merge-msg.c\n@@ -11,7 +11,7 @@ static const char * const fmt_merge_msg_usage[] = {\n \n int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)\n {\n-\tconst char *inpath = NULL;\n+\tchar *inpath = NULL;\n \tconst char *message = NULL;\n \tchar *into_name = NULL;\n \tint shortlog_len = -1;\n@@ -66,5 +66,7 @@ int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix)\n \tif (ret)\n \t\treturn ret;\n \twrite_in_full(STDOUT_FILENO, output.buf, output.len);\n+\n+\tfree(inpath);\n \treturn 0;\n }\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 78a247d8a9..4e4b645a21 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -2021,7 +2021,7 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tconst char *rfc = NULL;\n \tint creation_factor = -1;\n \tconst char *signature = git_version_string;\n-\tconst char *signature_file_arg = NULL;\n+\tchar *signature_file_arg = NULL;\n \tstruct keep_callback_data keep_callback_data = {\n \t\t.cfg = &cfg,\n \t\t.revs = &rev,\n@@ -2559,6 +2559,8 @@ int cmd_format_patch(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&rdiff1);\n \tstrbuf_release(&rdiff2);\n \tstrbuf_release(&rdiff_title);\n+\tfree(description_file);\n+\tfree(signature_file_arg);\n \tfree(to_free);\n \tfree(rev.message_id);\n \tif (rev.ref_message_ids)\ndiff --git a/builtin/multi-pack-index.c b/builtin/multi-pack-index.c\nindex 8360932d2e..9cf1a32d65 100644\n--- a/builtin/multi-pack-index.c\n+++ b/builtin/multi-pack-index.c\n@@ -50,7 +50,7 @@ static char const * const builtin_multi_pack_index_usage[] = {\n static struct opts_multi_pack_index {\n \tchar *object_dir;\n \tconst char *preferred_pack;\n-\tconst char *refs_snapshot;\n+\tchar *refs_snapshot;\n \tunsigned long batch_size;\n \tunsigned flags;\n \tint stdin_packs;\n@@ -135,6 +135,7 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \t\t\t     N_(\"refs snapshot for selecting bitmap commits\")),\n \t\tOPT_END(),\n \t};\n+\tint ret;\n \n \topts.flags |= MIDX_WRITE_BITMAP_HASH_CACHE;\n \n@@ -157,7 +158,6 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \n \tif (opts.stdin_packs) {\n \t\tstruct string_list packs = STRING_LIST_INIT_DUP;\n-\t\tint ret;\n \n \t\tread_packs_from_stdin(&packs);\n \n@@ -166,12 +166,17 @@ static int cmd_multi_pack_index_write(int argc, const char **argv,\n \t\t\t\t\t   opts.refs_snapshot, opts.flags);\n \n \t\tstring_list_clear(&packs, 0);\n+\t\tfree(opts.refs_snapshot);\n \n \t\treturn ret;\n \n \t}\n-\treturn write_midx_file(opts.object_dir, opts.preferred_pack,\n-\t\t\t       opts.refs_snapshot, opts.flags);\n+\n+\tret = write_midx_file(opts.object_dir, opts.preferred_pack,\n+\t\t\t      opts.refs_snapshot, opts.flags);\n+\n+\tfree(opts.refs_snapshot);\n+\treturn ret;\n }\n \n static int cmd_multi_pack_index_verify(int argc, const char **argv,\ndiff --git a/builtin/sparse-checkout.c b/builtin/sparse-checkout.c\nindex 0f52e25249..84a6adf681 100644\n--- a/builtin/sparse-checkout.c\n+++ b/builtin/sparse-checkout.c\n@@ -1011,6 +1011,7 @@ static int sparse_checkout_check_rules(int argc, const char **argv, const char *\n \n \tret = check_rules(&pl, check_rules_opts.null_termination);\n \tclear_pattern_list(&pl);\n+\tfree(check_rules_opts.rules_file);\n \treturn ret;\n }\n \ndiff --git a/t/helper/test-parse-options.c b/t/helper/test-parse-options.c\nindex ded8116cc5..5250913d99 100644\n--- a/t/helper/test-parse-options.c\n+++ b/t/helper/test-parse-options.c\n@@ -207,6 +207,7 @@ int cmd__parse_options(int argc, const char **argv)\n \texpect.strdup_strings = 1;\n \tstring_list_clear(&expect, 0);\n \tstring_list_clear(&list, 0);\n+\tfree(file);\n \n \treturn ret;\n }\ndiff --git a/t/t1512-rev-parse-disambiguation.sh b/t/t1512-rev-parse-disambiguation.sh\nindex 70f1e0a998..f9d68ce74e 100755\n--- a/t/t1512-rev-parse-disambiguation.sh\n+++ b/t/t1512-rev-parse-disambiguation.sh\n@@ -23,6 +23,7 @@ one tagged as v1.0.0.  They all have one regular file each.\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 test_cmp_failed_rev_parse () {\ndiff --git a/t/t2500-untracked-overwriting.sh b/t/t2500-untracked-overwriting.sh\nindex 5c0bf4d21f..714feb83be 100755\n--- a/t/t2500-untracked-overwriting.sh\n+++ b/t/t2500-untracked-overwriting.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test handling of overwriting untracked files'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_setup_reset () {\ndiff --git a/t/t3406-rebase-message.sh b/t/t3406-rebase-message.sh\nindex a1d7fa7f7c..82108b67e6 100755\n--- a/t/t3406-rebase-message.sh\n+++ b/t/t3406-rebase-message.sh\n@@ -5,6 +5,7 @@ test_description='messages from rebase operation'\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 test_expect_success 'setup' '\ndiff --git a/t/t3407-rebase-abort.sh b/t/t3407-rebase-abort.sh\nindex 9f49c4228b..2c3f38d45a 100755\n--- a/t/t3407-rebase-abort.sh\n+++ b/t/t3407-rebase-abort.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --abort tests'\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 test_expect_success setup '\ndiff --git a/t/t3428-rebase-signoff.sh b/t/t3428-rebase-signoff.sh\nindex 6f57aed9fa..365436ebfc 100755\n--- a/t/t3428-rebase-signoff.sh\n+++ b/t/t3428-rebase-signoff.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --signoff\n This test runs git rebase --signoff and make sure that it works.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-rebase.sh\n \ndiff --git a/t/t4131-apply-fake-ancestor.sh b/t/t4131-apply-fake-ancestor.sh\nindex b1361ce546..40c92115a6 100755\n--- a/t/t4131-apply-fake-ancestor.sh\n+++ b/t/t4131-apply-fake-ancestor.sh\n@@ -5,6 +5,7 @@\n \n test_description='git apply --build-fake-ancestor handling.'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t4151-am-abort.sh b/t/t4151-am-abort.sh\nindex edb38da701..1825a89d6a 100755\n--- a/t/t4151-am-abort.sh\n+++ b/t/t4151-am-abort.sh\n@@ -2,6 +2,7 @@\n \n test_description='am --abort'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t4253-am-keep-cr-dos.sh b/t/t4253-am-keep-cr-dos.sh\nindex 0ee69d2a0c..2bcdd9f34f 100755\n--- a/t/t4253-am-keep-cr-dos.sh\n+++ b/t/t4253-am-keep-cr-dos.sh\n@@ -9,6 +9,7 @@ test_description='git-am mbox with dos line ending.\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 # Three patches which will be added as files with dos line ending.\ndiff --git a/t/t4255-am-submodule.sh b/t/t4255-am-submodule.sh\nindex a7ba08f728..04f3ccfc41 100755\n--- a/t/t4255-am-submodule.sh\n+++ b/t/t4255-am-submodule.sh\n@@ -2,6 +2,7 @@\n \n test_description='git am handling submodules'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-submodule-update.sh\n \ndiff --git a/t/t5407-post-rewrite-hook.sh b/t/t5407-post-rewrite-hook.sh\nindex ad7f8c6f00..e99e728236 100755\n--- a/t/t5407-post-rewrite-hook.sh\n+++ b/t/t5407-post-rewrite-hook.sh\n@@ -7,6 +7,7 @@ test_description='Test the post-rewrite hook.'\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 test_expect_success 'setup' '\ndiff --git a/t/t6427-diff3-conflict-markers.sh b/t/t6427-diff3-conflict-markers.sh\nindex dd5fe6a402..a13271b349 100755\n--- a/t/t6427-diff3-conflict-markers.sh\n+++ b/t/t6427-diff3-conflict-markers.sh\n@@ -5,6 +5,7 @@ test_description='recursive merge diff3 style conflict markers'\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 # Setup:\ndiff --git a/t/t7512-status-help.sh b/t/t7512-status-help.sh\nindex 802f8f704c..cdd5f2c697 100755\n--- a/t/t7512-status-help.sh\n+++ b/t/t7512-status-help.sh\n@@ -10,6 +10,7 @@ test_description='git status advice'\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496863","messageId":"c5f49bf9305940f6d96a71468f07c3f0effd533e.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 03/29] notes-utils: free note trees when releasing copied notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:26Z","receivedAt":"2024-06-11T09:19:31Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While we clear most of the members of `struct notes_rewrite_cfg` in\n`finish_copy_notes_for_rewrite()`, we do not clear the notes tree. Fix\nthis to plug this memory leak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n notes-utils.c                         | 1 +\n t/t3400-rebase.sh                     | 1 +\n t/t7501-commit-basic-functionality.sh | 1 +\n 3 files changed, 3 insertions(+)\n\ndiff --git a/notes-utils.c b/notes-utils.c\nindex e33aa86c4b..671d1969b1 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -187,6 +187,7 @@ void finish_copy_notes_for_rewrite(struct repository *r,\n \tfor (i = 0; c->trees[i]; i++) {\n \t\tcommit_notes(r, c->trees[i], msg);\n \t\tfree_notes(c->trees[i]);\n+\t\tfree(c->trees[i]);\n \t}\n \tfree(c->trees);\n \tfree(c);\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex e1c8c5f701..ae34bfad60 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -11,6 +11,7 @@ among other things.\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 GIT_AUTHOR_NAME=author@name\ndiff --git a/t/t7501-commit-basic-functionality.sh b/t/t7501-commit-basic-functionality.sh\nindex cc12f99f11..52f5e28154 100755\n--- a/t/t7501-commit-basic-functionality.sh\n+++ b/t/t7501-commit-basic-functionality.sh\n@@ -9,6 +9,7 @@ test_description='git commit'\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 . \"$TEST_DIRECTORY/lib-diff.sh\"\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496864","messageId":"5a1eae2cc9624375154c7534d2f3ab2e487c1180.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 04/29] bundle: plug leaks in `create_bundle()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:31Z","receivedAt":"2024-06-11T09:19:36Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When creating a bundle, we set up a revision walk, but never release\ndata associated with it. Furthermore, we create a mostly-shallow copy of\nthat revision walk where we only adapt its pending objects such that we\ncan reuse the walk. While that copy must not be released, the pending\nobjects array need to be.\n\nPlug those memory leaks by releasing the revision walk and the pending\nobjects of the copied revision walk.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n bundle.c                | 29 +++++++++++++++++++----------\n t/t5605-clone-local.sh  |  1 +\n t/t5607-clone-bundle.sh |  1 +\n t/t6020-bundle-misc.sh  |  1 +\n 4 files changed, 22 insertions(+), 10 deletions(-)\n\ndiff --git a/bundle.c b/bundle.c\nindex 95367c2d0a..9af558c7a9 100644\n--- a/bundle.c\n+++ b/bundle.c\n@@ -500,6 +500,7 @@ int create_bundle(struct repository *r, const char *path,\n \tstruct rev_info revs, revs_copy;\n \tint min_version = 2;\n \tstruct bundle_prerequisites_info bpi;\n+\tint ret;\n \tint i;\n \n \t/* init revs to list objects for pack-objects later */\n@@ -525,8 +526,8 @@ int create_bundle(struct repository *r, const char *path,\n \t\tmin_version = 3;\n \n \tif (argc > 1) {\n-\t\terror(_(\"unrecognized argument: %s\"), argv[1]);\n-\t\tgoto err;\n+\t\tret = error(_(\"unrecognized argument: %s\"), argv[1]);\n+\t\tgoto out;\n \t}\n \n \tbundle_to_stdout = !strcmp(path, \"-\");\n@@ -591,23 +592,31 @@ int create_bundle(struct repository *r, const char *path,\n \n \t/* write bundle refs */\n \tref_count = write_bundle_refs(bundle_fd, &revs_copy);\n-\tif (!ref_count)\n+\tif (!ref_count) {\n \t\tdie(_(\"Refusing to create empty bundle.\"));\n-\telse if (ref_count < 0)\n-\t\tgoto err;\n+\t} else if (ref_count < 0) {\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n \n \t/* write pack */\n-\tif (write_pack_data(bundle_fd, &revs_copy, pack_options))\n-\t\tgoto err;\n+\tif (write_pack_data(bundle_fd, &revs_copy, pack_options)) {\n+\t\tret = -1;\n+\t\tgoto out;\n+\t}\n \n \tif (!bundle_to_stdout) {\n \t\tif (commit_lock_file(&lock))\n \t\t\tdie_errno(_(\"cannot create '%s'\"), path);\n \t}\n-\treturn 0;\n-err:\n+\n+\tret = 0;\n+\n+out:\n+\tobject_array_clear(&revs_copy.pending);\n+\trelease_revisions(&revs);\n \trollback_lock_file(&lock);\n-\treturn -1;\n+\treturn ret;\n }\n \n int unbundle(struct repository *r, struct bundle_header *header,\ndiff --git a/t/t5605-clone-local.sh b/t/t5605-clone-local.sh\nindex a3055869bc..9a1390a98f 100755\n--- a/t/t5605-clone-local.sh\n+++ b/t/t5605-clone-local.sh\n@@ -4,6 +4,7 @@ test_description='test local clone'\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 repo_is_hardlinked() {\ndiff --git a/t/t5607-clone-bundle.sh b/t/t5607-clone-bundle.sh\nindex 0d1e92d996..ac5ce9b648 100755\n--- a/t/t5607-clone-bundle.sh\n+++ b/t/t5607-clone-bundle.sh\n@@ -4,6 +4,7 @@ test_description='some bundle related tests'\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 test_expect_success 'setup' '\ndiff --git a/t/t6020-bundle-misc.sh b/t/t6020-bundle-misc.sh\nindex 3e6bcbf30c..fe75a06572 100755\n--- a/t/t6020-bundle-misc.sh\n+++ b/t/t6020-bundle-misc.sh\n@@ -8,6 +8,7 @@ test_description='Test git-bundle'\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 . \"$TEST_DIRECTORY\"/lib-bundle.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496865","messageId":"91eefcf64af0e74ce004ea605f7fcb59e7700d5c.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 05/29] biultin/rev-parse: fix memory leaks in `--parseopt` mode","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:36Z","receivedAt":"2024-06-11T09:19:40Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We have a bunch of memory leaks in git-rev-parse(1)'s `--parseopt` mode.\nRefactor the code to use `struct strvec`s to make it easier for us to\ntrack the lifecycle of those leaking variables and then free them.\n\nWhile at it, remove the unneeded static lifetime for some of the\nvariables.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rev-parse.c     | 53 +++++++++++++++++++++++------------------\n t/t5150-request-pull.sh |  1 +\n t/t7006-pager.sh        |  1 +\n 3 files changed, 32 insertions(+), 23 deletions(-)\n\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex 1e2919fd81..ab8a8f3b0e 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -423,12 +423,12 @@ static char *findspace(const char *s)\n \n static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n {\n-\tstatic int keep_dashdash = 0, stop_at_non_option = 0;\n-\tstatic char const * const parseopt_usage[] = {\n+\tint keep_dashdash = 0, stop_at_non_option = 0;\n+\tchar const * const parseopt_usage[] = {\n \t\tN_(\"git rev-parse --parseopt [<options>] -- [<args>...]\"),\n \t\tNULL\n \t};\n-\tstatic struct option parseopt_opts[] = {\n+\tstruct option parseopt_opts[] = {\n \t\tOPT_BOOL(0, \"keep-dashdash\", &keep_dashdash,\n \t\t\t\t\tN_(\"keep the `--` passed as an arg\")),\n \t\tOPT_BOOL(0, \"stop-at-non-option\", &stop_at_non_option,\n@@ -438,12 +438,11 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tN_(\"output in stuck long form\")),\n \t\tOPT_END(),\n \t};\n-\tstatic const char * const flag_chars = \"*=?!\";\n-\n \tstruct strbuf sb = STRBUF_INIT, parsed = STRBUF_INIT;\n-\tconst char **usage = NULL;\n+\tstruct strvec longnames = STRVEC_INIT;\n+\tstruct strvec usage = STRVEC_INIT;\n \tstruct option *opts = NULL;\n-\tint onb = 0, osz = 0, unb = 0, usz = 0;\n+\tsize_t opts_nr = 0, opts_alloc = 0;\n \n \tstrbuf_addstr(&parsed, \"set --\");\n \targc = parse_options(argc, argv, prefix, parseopt_opts, parseopt_usage,\n@@ -453,16 +452,16 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \n \t/* get the usage up to the first line with a -- on it */\n \tfor (;;) {\n+\t\tstrbuf_reset(&sb);\n \t\tif (strbuf_getline(&sb, stdin) == EOF)\n \t\t\tdie(_(\"premature end of input\"));\n-\t\tALLOC_GROW(usage, unb + 1, usz);\n \t\tif (!strcmp(\"--\", sb.buf)) {\n-\t\t\tif (unb < 1)\n+\t\t\tif (!usage.nr)\n \t\t\t\tdie(_(\"no usage string given before the `--' separator\"));\n-\t\t\tusage[unb] = NULL;\n \t\t\tbreak;\n \t\t}\n-\t\tusage[unb++] = strbuf_detach(&sb, NULL);\n+\n+\t\tstrvec_push(&usage, sb.buf);\n \t}\n \n \t/* parse: (<short>|<short>,<long>|<long>)[*=?!]*<arghint>? SP+ <help> */\n@@ -474,10 +473,10 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \t\tif (!sb.len)\n \t\t\tcontinue;\n \n-\t\tALLOC_GROW(opts, onb + 1, osz);\n-\t\tmemset(opts + onb, 0, sizeof(opts[onb]));\n+\t\tALLOC_GROW(opts, opts_nr + 1, opts_alloc);\n+\t\tmemset(opts + opts_nr, 0, sizeof(*opts));\n \n-\t\to = &opts[onb++];\n+\t\to = &opts[opts_nr++];\n \t\thelp = findspace(sb.buf);\n \t\tif (!help || sb.buf == help) {\n \t\t\to->type = OPTION_GROUP;\n@@ -494,20 +493,22 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \t\to->callback = &parseopt_dump;\n \n \t\t/* name(s) */\n-\t\ts = strpbrk(sb.buf, flag_chars);\n+\t\ts = strpbrk(sb.buf, \"*=?!\");\n \t\tif (!s)\n \t\t\ts = help;\n \n \t\tif (s == sb.buf)\n \t\t\tdie(_(\"missing opt-spec before option flags\"));\n \n-\t\tif (s - sb.buf == 1) /* short option only */\n+\t\tif (s - sb.buf == 1) { /* short option only */\n \t\t\to->short_name = *sb.buf;\n-\t\telse if (sb.buf[1] != ',') /* long option only */\n-\t\t\to->long_name = xmemdupz(sb.buf, s - sb.buf);\n-\t\telse {\n+\t\t} else if (sb.buf[1] != ',') { /* long option only */\n+\t\t\to->long_name = strvec_pushf(&longnames, \"%.*s\",\n+\t\t\t\t\t\t    (int)(s - sb.buf), sb.buf);\n+\t\t} else {\n \t\t\to->short_name = *sb.buf;\n-\t\t\to->long_name = xmemdupz(sb.buf + 2, s - sb.buf - 2);\n+\t\t\to->long_name = strvec_pushf(&longnames, \"%.*s\",\n+\t\t\t\t\t\t    (int)(s - sb.buf - 2), sb.buf + 2);\n \t\t}\n \n \t\t/* flags */\n@@ -537,9 +538,9 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \tstrbuf_release(&sb);\n \n \t/* put an OPT_END() */\n-\tALLOC_GROW(opts, onb + 1, osz);\n-\tmemset(opts + onb, 0, sizeof(opts[onb]));\n-\targc = parse_options(argc, argv, prefix, opts, usage,\n+\tALLOC_GROW(opts, opts_nr + 1, opts_alloc);\n+\tmemset(opts + opts_nr, 0, sizeof(*opts));\n+\targc = parse_options(argc, argv, prefix, opts, usage.v,\n \t\t\t(keep_dashdash ? PARSE_OPT_KEEP_DASHDASH : 0) |\n \t\t\t(stop_at_non_option ? PARSE_OPT_STOP_AT_NON_OPTION : 0) |\n \t\t\tPARSE_OPT_SHELL_EVAL);\n@@ -547,7 +548,13 @@ static int cmd_parseopt(int argc, const char **argv, const char *prefix)\n \tstrbuf_addstr(&parsed, \" --\");\n \tsq_quote_argv(&parsed, argv);\n \tputs(parsed.buf);\n+\n \tstrbuf_release(&parsed);\n+\tstrbuf_release(&sb);\n+\tstrvec_clear(&longnames);\n+\tstrvec_clear(&usage);\n+\tfree((char *) opts->help);\n+\tfree(opts);\n \treturn 0;\n }\n \ndiff --git a/t/t5150-request-pull.sh b/t/t5150-request-pull.sh\nindex cb67bac1c4..86bee33160 100755\n--- a/t/t5150-request-pull.sh\n+++ b/t/t5150-request-pull.sh\n@@ -5,6 +5,7 @@ test_description='Test workflows involving pull request.'\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 if ! test_have_prereq PERL\ndiff --git a/t/t7006-pager.sh b/t/t7006-pager.sh\nindex e56ca5b0fa..60e4c90de1 100755\n--- a/t/t7006-pager.sh\n+++ b/t/t7006-pager.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test automatic use of a pager.'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-pager.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496866","messageId":"f16c29a20a041d6d64681ed65ed051363d831235.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 06/29] merge-recursive: fix leaking rename conflict info","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:40Z","receivedAt":"2024-06-11T09:19:45Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When computing rename conflicts in our recursive merge algorithm we set\nup `struct rename_conflict_info`s to track that information. We never\nfree those data structures though and thus leak memory.\n\nWe need to be a bit more careful here though because the same rename\nconflict info can be assigned to multiple structures. Accommodate for\nthis by introducing a `rename_conflict_info_owned` bit that we can use\nto steer whether or not the rename conflict info shall be free'd.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-recursive.c                  | 8 +++++++-\n t/t3401-rebase-and-am-rename.sh    | 1 +\n t/t4153-am-resume-override-opts.sh | 1 +\n t/t7201-co.sh                      | 1 +\n 4 files changed, 10 insertions(+), 1 deletion(-)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8ff29ed09e..8c8e8b4868 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -239,7 +239,8 @@ enum rename_type {\n struct stage_data {\n \tstruct diff_filespec stages[4]; /* mostly for oid & mode; maybe path */\n \tstruct rename_conflict_info *rename_conflict_info;\n-\tunsigned processed:1;\n+\tunsigned processed:1,\n+\t\t rename_conflict_info_owned:1;\n };\n \n struct rename {\n@@ -308,6 +309,7 @@ static inline void setup_rename_conflict_info(enum rename_type rename_type,\n \n \tci->ren1->dst_entry->processed = 0;\n \tci->ren1->dst_entry->rename_conflict_info = ci;\n+\tci->ren1->dst_entry->rename_conflict_info_owned = 1;\n \tif (ren2) {\n \t\tci->ren2->dst_entry->rename_conflict_info = ci;\n \t}\n@@ -3055,6 +3057,10 @@ static void final_cleanup_rename(struct string_list *rename)\n \tfor (i = 0; i < rename->nr; i++) {\n \t\tre = rename->items[i].util;\n \t\tdiff_free_filepair(re->pair);\n+\t\tif (re->src_entry->rename_conflict_info_owned)\n+\t\t\tFREE_AND_NULL(re->src_entry->rename_conflict_info);\n+\t\tif (re->dst_entry->rename_conflict_info_owned)\n+\t\t\tFREE_AND_NULL(re->dst_entry->rename_conflict_info);\n \t}\n \tstring_list_clear(rename, 1);\n \tfree(rename);\ndiff --git a/t/t3401-rebase-and-am-rename.sh b/t/t3401-rebase-and-am-rename.sh\nindex f18bae9450..328c1d3a3f 100755\n--- a/t/t3401-rebase-and-am-rename.sh\n+++ b/t/t3401-rebase-and-am-rename.sh\n@@ -2,6 +2,7 @@\n \n test_description='git rebase + directory rename tests'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-rebase.sh\n \ndiff --git a/t/t4153-am-resume-override-opts.sh b/t/t4153-am-resume-override-opts.sh\nindex 4add7c7757..6bc377b917 100755\n--- a/t/t4153-am-resume-override-opts.sh\n+++ b/t/t4153-am-resume-override-opts.sh\n@@ -2,6 +2,7 @@\n \n test_description='git-am command-line options override saved options'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-terminal.sh\n \ndiff --git a/t/t7201-co.sh b/t/t7201-co.sh\nindex 42352dc0db..189d8e341b 100755\n--- a/t/t7201-co.sh\n+++ b/t/t7201-co.sh\n@@ -23,6 +23,7 @@ Test switching across them.\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 test_tick\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496867","messageId":"b9c3a34ae17e5e865ce3860934c990a3ed6213c2.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 07/29] revision: fix leaking display notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:45Z","receivedAt":"2024-06-11T09:19:50Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We never free the display notes options embedded into `struct revision`.\nImplement a new function `release_display_notes()` that we can call in\n`release_revisions()` to fix this.\n\nThere is another gotcha here though: we play some games with the string\nlist used to track extra notes refs, where we sometimes set the bit that\nindicates that strings should be strdup'd and sometimes unset it. This\ndance is done to avoid a copy of an already-allocated string when we\ncall `enable_ref_display_notes()`. But this dance is rather pointless as\nwe can instead call `string_list_append_nodup()` to transfer ownership\nof the allocated string to the list.\n\nRefactor the code to do so and drop the `strdup_strings` dance.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n notes.c          | 14 ++++++++------\n notes.h          |  5 +++++\n revision.c       |  1 +\n t/t3301-notes.sh |  1 +\n 4 files changed, 15 insertions(+), 6 deletions(-)\n\ndiff --git a/notes.c b/notes.c\nindex 53ca25c814..6a157e34ce 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1060,6 +1060,12 @@ void init_display_notes(struct display_notes_opt *opt)\n {\n \tmemset(opt, 0, sizeof(*opt));\n \topt->use_default_notes = -1;\n+\tstring_list_init_dup(&opt->extra_notes_refs);\n+}\n+\n+void release_display_notes(struct display_notes_opt *opt)\n+{\n+\tstring_list_clear(&opt->extra_notes_refs, 0);\n }\n \n void enable_default_display_notes(struct display_notes_opt *opt, int *show_notes)\n@@ -1073,19 +1079,15 @@ void enable_ref_display_notes(struct display_notes_opt *opt, int *show_notes,\n \tstruct strbuf buf = STRBUF_INIT;\n \tstrbuf_addstr(&buf, ref);\n \texpand_notes_ref(&buf);\n-\tstring_list_append(&opt->extra_notes_refs,\n-\t\t\tstrbuf_detach(&buf, NULL));\n+\tstring_list_append_nodup(&opt->extra_notes_refs,\n+\t\t\t\t strbuf_detach(&buf, NULL));\n \t*show_notes = 1;\n }\n \n void disable_display_notes(struct display_notes_opt *opt, int *show_notes)\n {\n \topt->use_default_notes = -1;\n-\t/* we have been strdup'ing ourselves, so trick\n-\t * string_list into free()ing strings */\n-\topt->extra_notes_refs.strdup_strings = 1;\n \tstring_list_clear(&opt->extra_notes_refs, 0);\n-\topt->extra_notes_refs.strdup_strings = 0;\n \t*show_notes = 0;\n }\n \ndiff --git a/notes.h b/notes.h\nindex 064fd7143a..235216944b 100644\n--- a/notes.h\n+++ b/notes.h\n@@ -275,6 +275,11 @@ struct display_notes_opt {\n  */\n void init_display_notes(struct display_notes_opt *opt);\n \n+/*\n+ * Release resources acquired by the display_notes_opt.\n+ */\n+void release_display_notes(struct display_notes_opt *opt);\n+\n /*\n  * This family of functions enables or disables the display of notes. In\n  * particular, 'enable_default_display_notes' will display the default notes,\ndiff --git a/revision.c b/revision.c\nindex af95502d92..75e71bcaea 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3168,6 +3168,7 @@ void release_revisions(struct rev_info *revs)\n {\n \tfree_commit_list(revs->commits);\n \tfree_commit_list(revs->ancestry_path_bottoms);\n+\trelease_display_notes(&revs->notes_opt);\n \tobject_array_clear(&revs->pending);\n \tobject_array_clear(&revs->boundary_commits);\n \trelease_revisions_cmdline(&revs->cmdline);\ndiff --git a/t/t3301-notes.sh b/t/t3301-notes.sh\nindex cf23c06c09..536bd11ff4 100755\n--- a/t/t3301-notes.sh\n+++ b/t/t3301-notes.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test commit notes'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n write_script fake_editor <<\\EOF\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496868","messageId":"3ea5e5c6bd2a3d85b7afe542e2a7e92676ae17ae.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 08/29] notes: fix memory leak when pruning notes","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:50Z","receivedAt":"2024-06-11T09:19:55Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `prune_notes()` we first store the notes that are to be deleted in a\nlocal list, and then iterate through that list to delete those notes one\nby one. We never free the list though and thus leak its memory. Fix\nthis.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n notes.c                | 7 ++++++-\n t/t3306-notes-prune.sh | 1 +\n 2 files changed, 7 insertions(+), 1 deletion(-)\n\ndiff --git a/notes.c b/notes.c\nindex 6a157e34ce..244b5c4b1c 100644\n--- a/notes.c\n+++ b/notes.c\n@@ -1219,11 +1219,16 @@ void prune_notes(struct notes_tree *t, int flags)\n \tfor_each_note(t, 0, prune_notes_helper, &l);\n \n \twhile (l) {\n+\t\tstruct note_delete_list *next;\n+\n \t\tif (flags & NOTES_PRUNE_VERBOSE)\n \t\t\tprintf(\"%s\\n\", hash_to_hex(l->sha1));\n \t\tif (!(flags & NOTES_PRUNE_DRYRUN))\n \t\t\tremove_note(t, l->sha1);\n-\t\tl = l->next;\n+\n+\t\tnext = l->next;\n+\t\tfree(l);\n+\t\tl = next;\n \t}\n }\n \ndiff --git a/t/t3306-notes-prune.sh b/t/t3306-notes-prune.sh\nindex 8f4102ff9e..b6e9f643e3 100755\n--- a/t/t3306-notes-prune.sh\n+++ b/t/t3306-notes-prune.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test git notes prune'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup: create a few commits with notes' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496869","messageId":"a0242359b70d60fb0236353b7bf1183f46d846a3.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 09/29] builtin/rev-list: fix leaking bitmap index when calculating disk usage","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:55Z","receivedAt":"2024-06-11T09:19:59Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"git-rev-list(1) can speed up its object size calculations for reachable\nobjects via a bitmap walk, if there is any bitmap. This is done in\n`try_bitmap_disk_usage()`, which tries to optimistically load the bitmap\nand then use it, if available. It never frees it though, leading to a\nmemory leak. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/rev-list.c     | 2 ++\n t/t6115-rev-list-du.sh | 2 ++\n 2 files changed, 4 insertions(+)\n\ndiff --git a/builtin/rev-list.c b/builtin/rev-list.c\nindex 77803727e0..97d077a994 100644\n--- a/builtin/rev-list.c\n+++ b/builtin/rev-list.c\n@@ -508,6 +508,8 @@ static int try_bitmap_disk_usage(struct rev_info *revs,\n \n \tsize_from_bitmap = get_disk_usage_from_bitmap(bitmap_git, revs);\n \tprint_disk_usage(size_from_bitmap);\n+\n+\tfree_bitmap_index(bitmap_git);\n \treturn 0;\n }\n \ndiff --git a/t/t6115-rev-list-du.sh b/t/t6115-rev-list-du.sh\nindex c0cfda62fa..21c4a211b1 100755\n--- a/t/t6115-rev-list-du.sh\n+++ b/t/t6115-rev-list-du.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='basic tests of rev-list --disk-usage'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # we want a mix of reachable and unreachable, as well as\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496870","messageId":"bdb738932856b8d8e3847471e515253f61d9d711.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 10/29] object-name: free leaking object contexts","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:19:59Z","receivedAt":"2024-06-11T09:20:04Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"While it is documented in `struct object_context::path` that this\nvariable needs to be released by the caller, this fact is rather easy to\nmiss given that we do not ever provide a function to release the object\ncontext. And of course, while some callers dutifully release the path,\nmany others don't.\n\nIntroduce a new `object_context_release()` function that releases the\npath. Convert callsites that used to free the path to use that new\nfunction and add missing calls to callsites that were leaking memory.\nRefactor those callsites as required to have a single return path, only.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/cat-file.c               | 17 ++++++----\n builtin/grep.c                   |  4 +--\n builtin/log.c                    |  6 ++--\n builtin/ls-tree.c                |  3 +-\n builtin/rev-parse.c              |  2 ++\n builtin/stash.c                  | 12 +++++--\n list-objects-filter.c            |  2 ++\n object-name.c                    | 40 ++++++++++++++++-------\n object-name.h                    |  2 ++\n revision.c                       | 55 ++++++++++++++++++++------------\n t/t7012-skip-worktree-writing.sh |  1 +\n 11 files changed, 97 insertions(+), 47 deletions(-)\n\ndiff --git a/builtin/cat-file.c b/builtin/cat-file.c\nindex 43a1d7ac49..18fe58d6b8 100644\n--- a/builtin/cat-file.c\n+++ b/builtin/cat-file.c\n@@ -102,7 +102,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,\n \tenum object_type type;\n \tchar *buf;\n \tunsigned long size;\n-\tstruct object_context obj_context;\n+\tstruct object_context obj_context = {0};\n \tstruct object_info oi = OBJECT_INFO_INIT;\n \tstruct strbuf sb = STRBUF_INIT;\n \tunsigned flags = OBJECT_INFO_LOOKUP_REPLACE;\n@@ -163,7 +163,8 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,\n \t\tgoto cleanup;\n \n \tcase 'e':\n-\t\treturn !repo_has_object_file(the_repository, &oid);\n+\t\tret = !repo_has_object_file(the_repository, &oid);\n+\t\tgoto cleanup;\n \n \tcase 'w':\n \n@@ -268,7 +269,7 @@ static int cat_one_file(int opt, const char *exp_type, const char *obj_name,\n \tret = 0;\n cleanup:\n \tfree(buf);\n-\tfree(obj_context.path);\n+\tobject_context_release(&obj_context);\n \treturn ret;\n }\n \n@@ -520,7 +521,7 @@ static void batch_one_object(const char *obj_name,\n \t\t\t     struct batch_options *opt,\n \t\t\t     struct expand_data *data)\n {\n-\tstruct object_context ctx;\n+\tstruct object_context ctx = {0};\n \tint flags =\n \t\tGET_OID_HASH_ANY |\n \t\t(opt->follow_symlinks ? GET_OID_FOLLOW_SYMLINKS : 0);\n@@ -557,7 +558,8 @@ static void batch_one_object(const char *obj_name,\n \t\t\tbreak;\n \t\t}\n \t\tfflush(stdout);\n-\t\treturn;\n+\n+\t\tgoto out;\n \t}\n \n \tif (ctx.mode == 0) {\n@@ -565,10 +567,13 @@ static void batch_one_object(const char *obj_name,\n \t\t       (uintmax_t)ctx.symlink_path.len,\n \t\t       opt->output_delim, ctx.symlink_path.buf, opt->output_delim);\n \t\tfflush(stdout);\n-\t\treturn;\n+\t\tgoto out;\n \t}\n \n \tbatch_object_write(obj_name, scratch, opt, data, NULL, 0);\n+\n+out:\n+\tobject_context_release(&ctx);\n }\n \n struct object_cb_data {\ndiff --git a/builtin/grep.c b/builtin/grep.c\nindex 5777ba82a9..dfc3c3e8bd 100644\n--- a/builtin/grep.c\n+++ b/builtin/grep.c\n@@ -1114,7 +1114,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \tfor (i = 0; i < argc; i++) {\n \t\tconst char *arg = argv[i];\n \t\tstruct object_id oid;\n-\t\tstruct object_context oc;\n+\t\tstruct object_context oc = {0};\n \t\tstruct object *object;\n \n \t\tif (!strcmp(arg, \"--\")) {\n@@ -1140,7 +1140,7 @@ int cmd_grep(int argc, const char **argv, const char *prefix)\n \t\tif (!seen_dashdash)\n \t\t\tverify_non_filename(prefix, arg);\n \t\tadd_object_array_with_path(object, arg, &list, oc.mode, oc.path);\n-\t\tfree(oc.path);\n+\t\tobject_context_release(&oc);\n \t}\n \n \t/*\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 4e4b645a21..37ecb3ff8b 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -682,7 +682,7 @@ static void show_tagger(const char *buf, struct rev_info *rev)\n static int show_blob_object(const struct object_id *oid, struct rev_info *rev, const char *obj_name)\n {\n \tstruct object_id oidc;\n-\tstruct object_context obj_context;\n+\tstruct object_context obj_context = {0};\n \tchar *buf;\n \tunsigned long size;\n \n@@ -698,7 +698,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \tif (!obj_context.path ||\n \t    !textconv_object(the_repository, obj_context.path,\n \t\t\t     obj_context.mode, &oidc, 1, &buf, &size)) {\n-\t\tfree(obj_context.path);\n+\t\tobject_context_release(&obj_context);\n \t\treturn stream_blob_to_fd(1, oid, NULL, 0);\n \t}\n \n@@ -706,7 +706,7 @@ static int show_blob_object(const struct object_id *oid, struct rev_info *rev, c\n \t\tdie(_(\"git show %s: bad file\"), obj_name);\n \n \twrite_or_die(1, buf, size);\n-\tfree(obj_context.path);\n+\tobject_context_release(&obj_context);\n \treturn 0;\n }\n \ndiff --git a/builtin/ls-tree.c b/builtin/ls-tree.c\nindex 7bf84b235c..bf372c67d7 100644\n--- a/builtin/ls-tree.c\n+++ b/builtin/ls-tree.c\n@@ -367,7 +367,7 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \t\tOPT_END()\n \t};\n \tstruct ls_tree_cmdmode_to_fmt *m2f = ls_tree_cmdmode_format;\n-\tstruct object_context obj_context;\n+\tstruct object_context obj_context = {0};\n \tint ret;\n \n \tgit_config(git_default_config, NULL);\n@@ -441,5 +441,6 @@ int cmd_ls_tree(int argc, const char **argv, const char *prefix)\n \n \tret = !!read_tree(the_repository, tree, &options.pathspec, fn, &options);\n \tclear_pathspec(&options.pathspec);\n+\tobject_context_release(&obj_context);\n \treturn ret;\n }\ndiff --git a/builtin/rev-parse.c b/builtin/rev-parse.c\nindex ab8a8f3b0e..2e64f5bda7 100644\n--- a/builtin/rev-parse.c\n+++ b/builtin/rev-parse.c\n@@ -1128,6 +1128,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t}\n \t\tif (!get_oid_with_context(the_repository, name,\n \t\t\t\t\t  flags, &oid, &unused)) {\n+\t\t\tobject_context_release(&unused);\n \t\t\tif (output_algo)\n \t\t\t\trepo_oid_to_algop(the_repository, &oid,\n \t\t\t\t\t\t  output_algo, &oid);\n@@ -1137,6 +1138,7 @@ int cmd_rev_parse(int argc, const char **argv, const char *prefix)\n \t\t\t\tshow_rev(type, &oid, name);\n \t\t\tcontinue;\n \t\t}\n+\t\tobject_context_release(&unused);\n \t\tif (verify)\n \t\t\tdie_no_single_rev(quiet);\n \t\tif (has_dashdash)\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 7859bc0866..628d848a0b 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1018,13 +1018,14 @@ static int store_stash(int argc, const char **argv, const char *prefix)\n \tint quiet = 0;\n \tconst char *stash_msg = NULL;\n \tstruct object_id obj;\n-\tstruct object_context dummy;\n+\tstruct object_context dummy = {0};\n \tstruct option options[] = {\n \t\tOPT__QUIET(&quiet, N_(\"be quiet\")),\n \t\tOPT_STRING('m', \"message\", &stash_msg, \"message\",\n \t\t\t   N_(\"stash message\")),\n \t\tOPT_END()\n \t};\n+\tint ret;\n \n \targc = parse_options(argc, argv, prefix, options,\n \t\t\t     git_stash_store_usage,\n@@ -1043,10 +1044,15 @@ static int store_stash(int argc, const char **argv, const char *prefix)\n \t\tif (!quiet)\n \t\t\tfprintf_ln(stderr, _(\"Cannot update %s with %s\"),\n \t\t\t\t\t     ref_stash, argv[0]);\n-\t\treturn -1;\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n \n-\treturn do_store_stash(&obj, stash_msg, quiet);\n+\tret = do_store_stash(&obj, stash_msg, quiet);\n+\n+out:\n+\tobject_context_release(&dummy);\n+\treturn ret;\n }\n \n static void add_pathspecs(struct strvec *args,\ndiff --git a/list-objects-filter.c b/list-objects-filter.c\nindex 4346f8da45..c95ec3509a 100644\n--- a/list-objects-filter.c\n+++ b/list-objects-filter.c\n@@ -542,6 +542,8 @@ static void filter_sparse_oid__init(\n \tfilter->filter_data = d;\n \tfilter->filter_object_fn = filter_sparse;\n \tfilter->free_fn = filter_sparse_free;\n+\n+\tobject_context_release(&oc);\n }\n \n /*\ndiff --git a/object-name.c b/object-name.c\nindex 523af6f64f..0471fafc98 100644\n--- a/object-name.c\n+++ b/object-name.c\n@@ -1757,6 +1757,11 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n \treturn check_refname_format(sb->buf, 0);\n }\n \n+void object_context_release(struct object_context *ctx)\n+{\n+\tfree(ctx->path);\n+}\n+\n /*\n  * This is like \"get_oid_basic()\", except it allows \"object ID expressions\",\n  * notably \"xyz^\" for \"parent of xyz\"\n@@ -1764,7 +1769,9 @@ int strbuf_check_branch_ref(struct strbuf *sb, const char *name)\n int repo_get_oid(struct repository *r, const char *name, struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, 0, oid, &unused);\n+\tint ret = get_oid_with_context(r, name, 0, oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n /*\n@@ -1802,8 +1809,10 @@ int repo_get_oid_committish(struct repository *r,\n \t\t\t    struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_COMMITTISH,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_COMMITTISH,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_treeish(struct repository *r,\n@@ -1811,8 +1820,10 @@ int repo_get_oid_treeish(struct repository *r,\n \t\t\t struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_TREEISH,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_TREEISH,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_commit(struct repository *r,\n@@ -1820,8 +1831,10 @@ int repo_get_oid_commit(struct repository *r,\n \t\t\tstruct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_COMMIT,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_COMMIT,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_tree(struct repository *r,\n@@ -1829,8 +1842,10 @@ int repo_get_oid_tree(struct repository *r,\n \t\t      struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_TREE,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_TREE,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n int repo_get_oid_blob(struct repository *r,\n@@ -1838,8 +1853,10 @@ int repo_get_oid_blob(struct repository *r,\n \t\t      struct object_id *oid)\n {\n \tstruct object_context unused;\n-\treturn get_oid_with_context(r, name, GET_OID_BLOB,\n-\t\t\t\t    oid, &unused);\n+\tint ret = get_oid_with_context(r, name, GET_OID_BLOB,\n+\t\t\t\t       oid, &unused);\n+\tobject_context_release(&unused);\n+\treturn ret;\n }\n \n /* Must be called only when object_name:filename doesn't exist. */\n@@ -2117,6 +2134,7 @@ void maybe_die_on_misspelt_object_name(struct repository *r,\n \tstruct object_id oid;\n \tget_oid_with_context_1(r, name, GET_OID_ONLY_TO_DIE | GET_OID_QUIETLY,\n \t\t\t       prefix, &oid, &oc);\n+\tobject_context_release(&oc);\n }\n \n enum get_oid_result get_oid_with_context(struct repository *repo,\ndiff --git a/object-name.h b/object-name.h\nindex 064ddc97d1..8dba4a47a4 100644\n--- a/object-name.h\n+++ b/object-name.h\n@@ -22,6 +22,8 @@ struct object_context {\n \tchar *path;\n };\n \n+void object_context_release(struct object_context *ctx);\n+\n /*\n  * Return an abbreviated sha1 unique within this repository's object database.\n  * The result will be at least `len` characters long, and will be NUL\ndiff --git a/revision.c b/revision.c\nindex 75e71bcaea..82c0aadb42 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -2130,30 +2130,26 @@ static int handle_dotdot(const char *arg,\n \t\t\t struct rev_info *revs, int flags,\n \t\t\t int cant_be_filename)\n {\n-\tstruct object_context a_oc, b_oc;\n+\tstruct object_context a_oc = {0}, b_oc = {0};\n \tchar *dotdot = strstr(arg, \"..\");\n \tint ret;\n \n \tif (!dotdot)\n \t\treturn -1;\n \n-\tmemset(&a_oc, 0, sizeof(a_oc));\n-\tmemset(&b_oc, 0, sizeof(b_oc));\n-\n \t*dotdot = '\\0';\n \tret = handle_dotdot_1(arg, dotdot, revs, flags, cant_be_filename,\n \t\t\t      &a_oc, &b_oc);\n \t*dotdot = '.';\n \n-\tfree(a_oc.path);\n-\tfree(b_oc.path);\n-\n+\tobject_context_release(&a_oc);\n+\tobject_context_release(&b_oc);\n \treturn ret;\n }\n \n static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int flags, unsigned revarg_opt)\n {\n-\tstruct object_context oc;\n+\tstruct object_context oc = {0};\n \tchar *mark;\n \tstruct object *object;\n \tstruct object_id oid;\n@@ -2161,6 +2157,7 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \tconst char *arg = arg_;\n \tint cant_be_filename = revarg_opt & REVARG_CANNOT_BE_FILENAME;\n \tunsigned get_sha1_flags = GET_OID_RECORD_PATH;\n+\tint ret;\n \n \tflags = flags & UNINTERESTING ? flags | BOTTOM : flags & ~BOTTOM;\n \n@@ -2169,17 +2166,22 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \t\t * Just \"..\"?  That is not a range but the\n \t\t * pathspec for the parent directory.\n \t\t */\n-\t\treturn -1;\n+\t\tret = -1;\n+\t\tgoto out;\n \t}\n \n-\tif (!handle_dotdot(arg, revs, flags, revarg_opt))\n-\t\treturn 0;\n+\tif (!handle_dotdot(arg, revs, flags, revarg_opt)) {\n+\t\tret = 0;\n+\t\tgoto out;\n+\t}\n \n \tmark = strstr(arg, \"^@\");\n \tif (mark && !mark[2]) {\n \t\t*mark = 0;\n-\t\tif (add_parents_only(revs, arg, flags, 0))\n-\t\t\treturn 0;\n+\t\tif (add_parents_only(revs, arg, flags, 0)) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t\t*mark = '^';\n \t}\n \tmark = strstr(arg, \"^!\");\n@@ -2194,8 +2196,10 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \n \t\tif (mark[2]) {\n \t\t\tif (strtol_i(mark + 2, 10, &exclude_parent) ||\n-\t\t\t    exclude_parent < 1)\n-\t\t\t\treturn -1;\n+\t\t\t    exclude_parent < 1) {\n+\t\t\t\tret = -1;\n+\t\t\t\tgoto out;\n+\t\t\t}\n \t\t}\n \n \t\t*mark = 0;\n@@ -2217,17 +2221,25 @@ static int handle_revision_arg_1(const char *arg_, struct rev_info *revs, int fl\n \t * should error out if we can't even get an oid, as\n \t * `--missing=print` should be able to report missing oids.\n \t */\n-\tif (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc))\n-\t\treturn revs->ignore_missing ? 0 : -1;\n+\tif (get_oid_with_context(revs->repo, arg, get_sha1_flags, &oid, &oc)) {\n+\t\tret = revs->ignore_missing ? 0 : -1;\n+\t\tgoto out;\n+\t}\n \tif (!cant_be_filename)\n \t\tverify_non_filename(revs->prefix, arg);\n \tobject = get_reference(revs, arg, &oid, flags ^ local_flags);\n-\tif (!object)\n-\t\treturn (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1;\n+\tif (!object) {\n+\t\tret = (revs->ignore_missing || revs->do_not_die_on_missing_objects) ? 0 : -1;\n+\t\tgoto out;\n+\t}\n \tadd_rev_cmdline(revs, object, arg_, REV_CMD_REV, flags ^ local_flags);\n \tadd_pending_object_with_path(revs, object, arg, oc.mode, oc.path);\n-\tfree(oc.path);\n-\treturn 0;\n+\n+\tret = 0;\n+\n+out:\n+\tobject_context_release(&oc);\n+\treturn ret;\n }\n \n int handle_revision_arg(const char *arg, struct rev_info *revs, int flags, unsigned revarg_opt)\n@@ -3062,6 +3074,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, struct s\n \t\t\tdiagnose_missing_default(revs->def);\n \t\tobject = get_reference(revs, revs->def, &oid, 0);\n \t\tadd_pending_object_with_mode(revs, object, revs->def, oc.mode);\n+\t\tobject_context_release(&oc);\n \t}\n \n \t/* Did the user ask for any diff output? Run the diff! */\ndiff --git a/t/t7012-skip-worktree-writing.sh b/t/t7012-skip-worktree-writing.sh\nindex cd5c20fe51..d984200c17 100755\n--- a/t/t7012-skip-worktree-writing.sh\n+++ b/t/t7012-skip-worktree-writing.sh\n@@ -5,6 +5,7 @@\n \n test_description='test worktree writing operations when skip-worktree is used'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496871","messageId":"e6ff9a3f1c39a1cb69e8045268577174c6a0123c.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 11/29] builtin/difftool: plug memory leaks in `run_dir_diff()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:04Z","receivedAt":"2024-06-11T09:20:09Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're leaking a bunch of memory leaks in `run_dir_diff()`. Plug them.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/difftool.c | 3 +++\n 1 file changed, 3 insertions(+)\n\ndiff --git a/builtin/difftool.c b/builtin/difftool.c\nindex a1794b7eed..dcc68e190c 100644\n--- a/builtin/difftool.c\n+++ b/builtin/difftool.c\n@@ -662,6 +662,9 @@ static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix,\n \n \tfree(lbase_dir);\n \tfree(rbase_dir);\n+\tstrbuf_release(&info);\n+\tstrbuf_release(&lpath);\n+\tstrbuf_release(&rpath);\n \tstrbuf_release(&ldir);\n \tstrbuf_release(&rdir);\n \tstrbuf_release(&wtdir);\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496872","messageId":"fb30a254abaf132aa3e44b1edea927cf91e1d86f.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 12/29] builtin/merge-recursive: fix leaking object ID bases","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:09Z","receivedAt":"2024-06-11T09:20:13Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `cmd_merge_recursive()` we have a static array of object ID bases\nthat we pass to `merge_recursive_generic()`. This interface is somewhat\nweird though because the latter function accepts a pointer to a pointer\nof object IDs, which requires us to allocate the object IDs on the heap.\nAnd as we never free those object IDs, the end result is a leak.\n\nWhile we can easily solve this leak by just freeing the respective\nobject IDs, the whole calling convention is somewhat weird. Instead,\nrefactor `merge_recursive_generic()` to accept a plain pointer to object\nIDs so that we can avoid allocating them altogether.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/am.c                              | 6 +++---\n builtin/merge-recursive.c                 | 6 ++----\n merge-recursive.c                         | 8 ++++----\n merge-recursive.h                         | 2 +-\n t/t6432-merge-recursive-space-options.sh  | 1 +\n t/t6434-merge-recursive-rename-options.sh | 1 +\n 6 files changed, 12 insertions(+), 12 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 36839029d2..4ba44e2d70 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1573,8 +1573,8 @@ static int build_fake_ancestor(const struct am_state *state, const char *index_f\n  */\n static int fall_back_threeway(const struct am_state *state, const char *index_path)\n {\n-\tstruct object_id orig_tree, their_tree, our_tree;\n-\tconst struct object_id *bases[1] = { &orig_tree };\n+\tstruct object_id their_tree, our_tree;\n+\tstruct object_id bases[1] = { 0 };\n \tstruct merge_options o;\n \tstruct commit *result;\n \tchar *their_tree_name;\n@@ -1588,7 +1588,7 @@ static int fall_back_threeway(const struct am_state *state, const char *index_pa\n \tdiscard_index(the_repository->index);\n \tread_index_from(the_repository->index, index_path, get_git_dir());\n \n-\tif (write_index_as_tree(&orig_tree, the_repository->index, index_path, 0, NULL))\n+\tif (write_index_as_tree(&bases[0], the_repository->index, index_path, 0, NULL))\n \t\treturn error(_(\"Repository lacks necessary blobs to fall back on 3-way merge.\"));\n \n \tsay(state, stdout, _(\"Using index info to reconstruct a base tree...\"));\ndiff --git a/builtin/merge-recursive.c b/builtin/merge-recursive.c\nindex c2ce044a20..82bebea15b 100644\n--- a/builtin/merge-recursive.c\n+++ b/builtin/merge-recursive.c\n@@ -23,7 +23,7 @@ static char *better_branch_name(const char *branch)\n \n int cmd_merge_recursive(int argc, const char **argv, const char *prefix UNUSED)\n {\n-\tconst struct object_id *bases[21];\n+\tstruct object_id bases[21];\n \tunsigned bases_count = 0;\n \tint i, failed;\n \tstruct object_id h1, h2;\n@@ -49,10 +49,8 @@ int cmd_merge_recursive(int argc, const char **argv, const char *prefix UNUSED)\n \t\t\tcontinue;\n \t\t}\n \t\tif (bases_count < ARRAY_SIZE(bases)-1) {\n-\t\t\tstruct object_id *oid = xmalloc(sizeof(struct object_id));\n-\t\t\tif (repo_get_oid(the_repository, argv[i], oid))\n+\t\t\tif (repo_get_oid(the_repository, argv[i], &bases[bases_count++]))\n \t\t\t\tdie(_(\"could not parse object '%s'\"), argv[i]);\n-\t\t\tbases[bases_count++] = oid;\n \t\t}\n \t\telse\n \t\t\twarning(Q_(\"cannot handle more than %d base. \"\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 8c8e8b4868..eff73dac02 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3866,7 +3866,7 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n-\t\t\t    const struct object_id **merge_bases,\n+\t\t\t    const struct object_id *merge_bases,\n \t\t\t    struct commit **result)\n {\n \tint clean;\n@@ -3879,10 +3879,10 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\tint i;\n \t\tfor (i = 0; i < num_merge_bases; ++i) {\n \t\t\tstruct commit *base;\n-\t\t\tif (!(base = get_ref(opt->repo, merge_bases[i],\n-\t\t\t\t\t     oid_to_hex(merge_bases[i]))))\n+\t\t\tif (!(base = get_ref(opt->repo, &merge_bases[i],\n+\t\t\t\t\t     oid_to_hex(&merge_bases[i]))))\n \t\t\t\treturn err(opt, _(\"Could not parse object '%s'\"),\n-\t\t\t\t\t   oid_to_hex(merge_bases[i]));\n+\t\t\t\t\t   oid_to_hex(&merge_bases[i]));\n \t\t\tcommit_list_insert(base, &ca);\n \t\t}\n \t\tif (num_merge_bases == 1)\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex e67d38c303..839eb6436e 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -123,7 +123,7 @@ int merge_recursive_generic(struct merge_options *opt,\n \t\t\t    const struct object_id *head,\n \t\t\t    const struct object_id *merge,\n \t\t\t    int num_merge_bases,\n-\t\t\t    const struct object_id **merge_bases,\n+\t\t\t    const struct object_id *merge_bases,\n \t\t\t    struct commit **result);\n \n #endif\ndiff --git a/t/t6432-merge-recursive-space-options.sh b/t/t6432-merge-recursive-space-options.sh\nindex db4b77e63d..c93538b0c3 100755\n--- a/t/t6432-merge-recursive-space-options.sh\n+++ b/t/t6432-merge-recursive-space-options.sh\n@@ -14,6 +14,7 @@ test_description='merge-recursive space options\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 test_have_prereq SED_STRIPS_CR && SED_OPTIONS=-b\ndiff --git a/t/t6434-merge-recursive-rename-options.sh b/t/t6434-merge-recursive-rename-options.sh\nindex a11707835b..df1d0c156c 100755\n--- a/t/t6434-merge-recursive-rename-options.sh\n+++ b/t/t6434-merge-recursive-rename-options.sh\n@@ -29,6 +29,7 @@ mentions this in a different context).\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 get_expected_stages () {\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496873","messageId":"b6540e4aff9a355a62700983681efc3ea4419a05.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 13/29] merge-recursive: fix memory leak when finalizing merge","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:14Z","receivedAt":"2024-06-11T09:20:18Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We do not free some members of `struct merge_options`' private data.\nFix this to plug those leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n merge-recursive.c               | 3 +++\n t/t1004-read-tree-m-u-wf.sh     | 1 +\n t/t1015-read-index-unmerged.sh  | 2 ++\n t/t3509-cherry-pick-merge-df.sh | 1 +\n 4 files changed, 7 insertions(+)\n\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex eff73dac02..832c8ef3f3 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3800,6 +3800,9 @@ static void merge_finalize(struct merge_options *opt)\n \tif (show(opt, 2))\n \t\tdiff_warn_rename_limit(\"merge.renamelimit\",\n \t\t\t\t       opt->priv->needed_rename_limit, 0);\n+\thashmap_clear_and_free(&opt->priv->current_file_dir_set,\n+\t\t\t       struct path_hashmap_entry, e);\n+\tstring_list_clear(&opt->priv->df_conflict_file_set, 0);\n \tFREE_AND_NULL(opt->priv);\n }\n \ndiff --git a/t/t1004-read-tree-m-u-wf.sh b/t/t1004-read-tree-m-u-wf.sh\nindex 11bf10424f..2b9720b0fe 100755\n--- a/t/t1004-read-tree-m-u-wf.sh\n+++ b/t/t1004-read-tree-m-u-wf.sh\n@@ -5,6 +5,7 @@ test_description='read-tree -m -u checks working tree files'\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 . \"$TEST_DIRECTORY\"/lib-read-tree.sh\n \ndiff --git a/t/t1015-read-index-unmerged.sh b/t/t1015-read-index-unmerged.sh\nindex 55d22da32c..da737a32a2 100755\n--- a/t/t1015-read-index-unmerged.sh\n+++ b/t/t1015-read-index-unmerged.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='Test various callers of read_index_unmerged'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup modify/delete + directory/file conflict' '\ndiff --git a/t/t3509-cherry-pick-merge-df.sh b/t/t3509-cherry-pick-merge-df.sh\nindex f4159246e1..171cc6d76b 100755\n--- a/t/t3509-cherry-pick-merge-df.sh\n+++ b/t/t3509-cherry-pick-merge-df.sh\n@@ -4,6 +4,7 @@ test_description='Test cherry-pick with directory/file conflicts'\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 test_expect_success 'Initialize repository' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496874","messageId":"7cb4f7517865c5187bf0d1061a9f34776ad66290.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 14/29] builtin/log: fix leaking commit list in git-cherry(1)","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:19Z","receivedAt":"2024-06-11T09:20:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're storing the list of commits that git-cherry(1) is about to print\ninto a temporary list. This list is never getting free'd and thus leaks.\nFix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/log.c     | 6 +++---\n t/t3500-cherry.sh | 1 +\n 2 files changed, 4 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/log.c b/builtin/log.c\nindex 37ecb3ff8b..b36fa9d155 100644\n--- a/builtin/log.c\n+++ b/builtin/log.c\n@@ -2675,16 +2675,16 @@ int cmd_cherry(int argc, const char **argv, const char *prefix)\n \t\tcommit_list_insert(commit, &list);\n \t}\n \n-\twhile (list) {\n+\tfor (struct commit_list *l = list; l; l = l->next) {\n \t\tchar sign = '+';\n \n-\t\tcommit = list->item;\n+\t\tcommit = l->item;\n \t\tif (has_commit_patch_id(commit, &ids))\n \t\t\tsign = '-';\n \t\tprint_commit(sign, commit, verbose, abbrev, revs.diffopt.file);\n-\t\tlist = list->next;\n \t}\n \n+\tfree_commit_list(list);\n \tfree_patch_ids(&ids);\n \treturn 0;\n }\ndiff --git a/t/t3500-cherry.sh b/t/t3500-cherry.sh\nindex 78c3eac54b..61ca87512d 100755\n--- a/t/t3500-cherry.sh\n+++ b/t/t3500-cherry.sh\n@@ -11,6 +11,7 @@ checks that git cherry only returns the second patch in the local branch\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 GIT_AUTHOR_EMAIL=bogus_email_address\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496875","messageId":"99f4f3d3412a40eb6ada67ae48c7369cb57762cc.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 15/29] revision: free diff options","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:24Z","receivedAt":"2024-06-11T09:20:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There is a todo comment in `release_revisions()` that mentions that we\nneed to free the diff options, which was added via 54c8a7c379 (revisions\nAPI: add a TODO for diff_free(&revs->diffopt), 2022-04-14). Releasing\nthe diff options wasn't quite feasible at that time because some call\nsites rely on its contents to remain even after the revisions have been\nreleased.\n\nIn fact, there really only are a couple of callsites that misbehave\nhere:\n\n  - `cmd_shortlog()` releases the revisions, but continues to access its\n    file pointer.\n\n  - `do_diff_cache()` creates a shallow copy of `struct diff_options`,\n    but does not set the `no_free` member. Consequently, we end up\n    releasing resources of the caller-provided diff options.\n\n  - `diff_free()` and friends do not play nice when being called\n    multiple times as they don't unset data structures that they have\n    just released.\n\nFix all of those cases and enable the call to `diff_free()`, which plugs\na bunch of memory leaks.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/shortlog.c                        | 5 +----\n diff-lib.c                                | 2 ++\n diff.c                                    | 8 ++++++--\n revision.c                                | 2 +-\n t/t4208-log-magic-pathspec.sh             | 1 +\n t/t6000-rev-list-misc.sh                  | 1 +\n t/t6001-rev-list-graft.sh                 | 1 +\n t/t6013-rev-list-reverse-parents.sh       | 1 +\n t/t6017-rev-list-stdin.sh                 | 1 +\n t/t9500-gitweb-standalone-no-errors.sh    | 1 +\n t/t9502-gitweb-standalone-parse-output.sh | 1 +\n 11 files changed, 17 insertions(+), 7 deletions(-)\n\ndiff --git a/builtin/shortlog.c b/builtin/shortlog.c\nindex d4daf31e22..5bde7c68c2 100644\n--- a/builtin/shortlog.c\n+++ b/builtin/shortlog.c\n@@ -460,11 +460,8 @@ int cmd_shortlog(int argc, const char **argv, const char *prefix)\n \telse\n \t\tget_from_rev(&rev, &log);\n \n-\trelease_revisions(&rev);\n-\n \tshortlog_output(&log);\n-\tif (log.file != stdout)\n-\t\tfclose(log.file);\n+\trelease_revisions(&rev);\n \treturn 0;\n }\n \ndiff --git a/diff-lib.c b/diff-lib.c\nindex 5a5a50c5a1..1cbf03bf39 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -662,9 +662,11 @@ int do_diff_cache(const struct object_id *tree_oid, struct diff_options *opt)\n \trepo_init_revisions(opt->repo, &revs, NULL);\n \tcopy_pathspec(&revs.prune_data, &opt->pathspec);\n \trevs.diffopt = *opt;\n+\trevs.diffopt.no_free = 1;\n \n \tif (diff_cache(&revs, tree_oid, NULL, 1))\n \t\texit(128);\n+\n \trelease_revisions(&revs);\n \treturn 0;\n }\ndiff --git a/diff.c b/diff.c\nindex e70301df76..53e2f5a42e 100644\n--- a/diff.c\n+++ b/diff.c\n@@ -6649,8 +6649,10 @@ static void diff_flush_patch_all_file_pairs(struct diff_options *o)\n \n static void diff_free_file(struct diff_options *options)\n {\n-\tif (options->close_file)\n+\tif (options->close_file && options->file) {\n \t\tfclose(options->file);\n+\t\toptions->file = NULL;\n+\t}\n }\n \n static void diff_free_ignore_regex(struct diff_options *options)\n@@ -6661,7 +6663,9 @@ static void diff_free_ignore_regex(struct diff_options *options)\n \t\tregfree(options->ignore_regex[i]);\n \t\tfree(options->ignore_regex[i]);\n \t}\n-\tfree(options->ignore_regex);\n+\n+\tFREE_AND_NULL(options->ignore_regex);\n+\toptions->ignore_regex_nr = 0;\n }\n \n void diff_free(struct diff_options *options)\ndiff --git a/revision.c b/revision.c\nindex 82c0aadb42..99c75c939d 100644\n--- a/revision.c\n+++ b/revision.c\n@@ -3191,7 +3191,7 @@ void release_revisions(struct rev_info *revs)\n \trelease_revisions_mailmap(revs->mailmap);\n \tfree_grep_patterns(&revs->grep_filter);\n \tgraph_clear(revs->graph);\n-\t/* TODO (need to handle \"no_free\"): diff_free(&revs->diffopt) */\n+\tdiff_free(&revs->diffopt);\n \tdiff_free(&revs->pruning);\n \treflog_walk_info_release(revs->reflog_info);\n \trelease_revisions_topo_walk_info(revs->topo_walk_info);\ndiff --git a/t/t4208-log-magic-pathspec.sh b/t/t4208-log-magic-pathspec.sh\nindex 806b2809d4..2a46eb6bed 100755\n--- a/t/t4208-log-magic-pathspec.sh\n+++ b/t/t4208-log-magic-pathspec.sh\n@@ -5,6 +5,7 @@ test_description='magic pathspec tests using git-log'\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 test_expect_success 'setup' '\ndiff --git a/t/t6000-rev-list-misc.sh b/t/t6000-rev-list-misc.sh\nindex 6289a2e8b0..f6d17ee902 100755\n--- a/t/t6000-rev-list-misc.sh\n+++ b/t/t6000-rev-list-misc.sh\n@@ -5,6 +5,7 @@ test_description='miscellaneous rev-list tests'\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 test_expect_success setup '\ndiff --git a/t/t6001-rev-list-graft.sh b/t/t6001-rev-list-graft.sh\nindex 73a2465aa0..3553bbbfe7 100755\n--- a/t/t6001-rev-list-graft.sh\n+++ b/t/t6001-rev-list-graft.sh\n@@ -5,6 +5,7 @@ test_description='Revision traversal vs grafts and path limiter'\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 test_expect_success setup '\ndiff --git a/t/t6013-rev-list-reverse-parents.sh b/t/t6013-rev-list-reverse-parents.sh\nindex 39793cbbd6..4128269c1d 100755\n--- a/t/t6013-rev-list-reverse-parents.sh\n+++ b/t/t6013-rev-list-reverse-parents.sh\n@@ -5,6 +5,7 @@ test_description='--reverse combines with --parents'\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 \ndiff --git a/t/t6017-rev-list-stdin.sh b/t/t6017-rev-list-stdin.sh\nindex 4821b90e74..a0a40fe55c 100755\n--- a/t/t6017-rev-list-stdin.sh\n+++ b/t/t6017-rev-list-stdin.sh\n@@ -8,6 +8,7 @@ test_description='log family learns --stdin'\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 () {\ndiff --git a/t/t9500-gitweb-standalone-no-errors.sh b/t/t9500-gitweb-standalone-no-errors.sh\nindex 7679780fb8..ccfa415384 100755\n--- a/t/t9500-gitweb-standalone-no-errors.sh\n+++ b/t/t9500-gitweb-standalone-no-errors.sh\n@@ -13,6 +13,7 @@ or warnings to log.'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./lib-gitweb.sh\n \n # ----------------------------------------------------------------------\ndiff --git a/t/t9502-gitweb-standalone-parse-output.sh b/t/t9502-gitweb-standalone-parse-output.sh\nindex 81d5625557..b41ea19331 100755\n--- a/t/t9502-gitweb-standalone-parse-output.sh\n+++ b/t/t9502-gitweb-standalone-parse-output.sh\n@@ -13,6 +13,7 @@ in the HTTP header or the actual script output.'\n GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./lib-gitweb.sh\n \n # ----------------------------------------------------------------------\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496876","messageId":"707d798f7873eb772c724774e1fe6d3b499c759d.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 16/29] builtin/stash: fix leak in `show_stash()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:28Z","receivedAt":"2024-06-11T09:20:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We leak the `revision_args()` variable. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/stash.c              | 2 ++\n t/t3420-rebase-autostash.sh  | 1 +\n t/t3907-stash-show-config.sh | 1 +\n 3 files changed, 4 insertions(+)\n\ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 628d848a0b..1ed0a9a5d9 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -975,7 +975,9 @@ static int show_stash(int argc, const char **argv, const char *prefix)\n \tlog_tree_diff_flush(&rev);\n \n \tret = diff_result_code(&rev.diffopt);\n+\n cleanup:\n+\tstrvec_clear(&revision_args);\n \tstrvec_clear(&stash_args);\n \tfree_stash_info(&info);\n \trelease_revisions(&rev);\ndiff --git a/t/t3420-rebase-autostash.sh b/t/t3420-rebase-autostash.sh\nindex 1a820f1481..63e400b89f 100755\n--- a/t/t3420-rebase-autostash.sh\n+++ b/t/t3420-rebase-autostash.sh\n@@ -7,6 +7,7 @@ test_description='git rebase --autostash tests'\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 test_expect_success setup '\ndiff --git a/t/t3907-stash-show-config.sh b/t/t3907-stash-show-config.sh\nindex 10914bba7b..7a2eb98b86 100755\n--- a/t/t3907-stash-show-config.sh\n+++ b/t/t3907-stash-show-config.sh\n@@ -2,6 +2,7 @@\n \n test_description='Test git stash show configuration.'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496877","messageId":"7d73c50872bbc080689b983312879af77312a708.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 17/29] rerere: fix various trivial leaks","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:33Z","receivedAt":"2024-06-11T09:20:37Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We leak various different string lists in the rerere code. Free those to\nplug them.\n\nNote that the `merge_rr` variable is intentionally being free'd with the\n`free_util` parameter set to 1. The `util` field is used there to store\nthe IDs of every rerere item and thus needs to be freed, as well.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n rerere.c                      | 3 +++\n t/t1021-rerere-in-workdir.sh  | 1 +\n t/t3504-cherry-pick-rerere.sh | 1 +\n t/t7600-merge.sh              | 1 +\n 4 files changed, 6 insertions(+)\n\ndiff --git a/rerere.c b/rerere.c\nindex c7e1f8fd25..10382da55c 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -849,6 +849,8 @@ static int do_plain_rerere(struct repository *r,\n \tif (update.nr)\n \t\tupdate_paths(r, &update);\n \n+\tstring_list_clear(&conflict, 0);\n+\tstring_list_clear(&update, 0);\n \treturn write_rr(rr, fd);\n }\n \n@@ -912,6 +914,7 @@ int repo_rerere(struct repository *r, int flags)\n \t\treturn 0;\n \tstatus = do_plain_rerere(r, &merge_rr, fd);\n \tfree_rerere_dirs();\n+\tstring_list_clear(&merge_rr, 1);\n \treturn status;\n }\n \ndiff --git a/t/t1021-rerere-in-workdir.sh b/t/t1021-rerere-in-workdir.sh\nindex 0b892894eb..69bf9476cb 100755\n--- a/t/t1021-rerere-in-workdir.sh\n+++ b/t/t1021-rerere-in-workdir.sh\n@@ -4,6 +4,7 @@ test_description='rerere run in a workdir'\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 test_expect_success SYMLINKS setup '\ndiff --git a/t/t3504-cherry-pick-rerere.sh b/t/t3504-cherry-pick-rerere.sh\nindex 4581ae98b8..597c98e9c5 100755\n--- a/t/t3504-cherry-pick-rerere.sh\n+++ b/t/t3504-cherry-pick-rerere.sh\n@@ -5,6 +5,7 @@ test_description='cherry-pick should rerere for conflicts'\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 test_expect_success setup '\ndiff --git a/t/t7600-merge.sh b/t/t7600-merge.sh\nindex e5ff073099..041f1077b0 100755\n--- a/t/t7600-merge.sh\n+++ b/t/t7600-merge.sh\n@@ -29,6 +29,7 @@ Testing basic merge operations/option parsing.\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 . \"$TEST_DIRECTORY\"/lib-gpg.sh\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496878","messageId":"1700ec8d215d244ad6787880cb25b9acb83fcddd.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 18/29] config: fix leaking \"core.notesref\" variable","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:38Z","receivedAt":"2024-06-11T09:20:43Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"The variable used to track the \"core.notesref\" config is not getting\nfreed before we assign to it and thus leaks. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n config.c                            | 1 +\n t/t3308-notes-merge.sh              | 1 +\n t/t3309-notes-merge-auto-resolve.sh | 1 +\n 3 files changed, 3 insertions(+)\n\ndiff --git a/config.c b/config.c\nindex abce05b774..742175c130 100644\n--- a/config.c\n+++ b/config.c\n@@ -1565,6 +1565,7 @@ static int git_default_core_config(const char *var, const char *value,\n \tif (!strcmp(var, \"core.notesref\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n+\t\tfree(notes_ref_name);\n \t\tnotes_ref_name = xstrdup(value);\n \t\treturn 0;\n \t}\ndiff --git a/t/t3308-notes-merge.sh b/t/t3308-notes-merge.sh\nindex 202702be1a..e1d05ff6bc 100755\n--- a/t/t3308-notes-merge.sh\n+++ b/t/t3308-notes-merge.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test merging of notes trees'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t3309-notes-merge-auto-resolve.sh b/t/t3309-notes-merge-auto-resolve.sh\nindex 9bd5dbf341..f55277f499 100755\n--- a/t/t3309-notes-merge-auto-resolve.sh\n+++ b/t/t3309-notes-merge-auto-resolve.sh\n@@ -5,6 +5,7 @@\n \n test_description='Test notes merging with auto-resolving strategies'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Set up a notes merge scenario with all kinds of potential conflicts\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496879","messageId":"c1a1eb34aba9e824a7890f3b1429ceaf6a9cf7eb.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 19/29] commit: fix leaking parents when calling `commit_tree_extended()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:42Z","receivedAt":"2024-06-11T09:20:47Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When creating commits via `commit_tree_extended()`, the caller passes in\na string list of parents. This call implicitly transfers ownership of\nthat list to the function, which is quite surprising to begin with. But\nto make matters worse, `commit_tree_extended()` doesn't even bother to\nfree the list of parents in error cases. The result is a memory leak,\nand one that the caller cannot fix by themselves because they do not\nknow whether parts of the string list have already been released.\n\nRefactor the code such that callers can keep ownership of the list of\nparents, which is getting indicated by parameter being a constant\npointer now. Free the lists at the calling site and add a common exit\npath to those sites as required.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/am.c                       |  1 +\n builtin/commit-tree.c              | 11 ++++++++---\n builtin/commit.c                   |  3 ++-\n builtin/merge.c                    |  6 +++++-\n builtin/replay.c                   | 14 +++++++++-----\n builtin/stash.c                    |  9 ++++-----\n commit.c                           | 26 ++++++++++++--------------\n commit.h                           | 10 +++++-----\n notes-merge.c                      |  1 +\n notes-utils.c                      |  8 ++++++--\n notes-utils.h                      |  2 +-\n sequencer.c                        |  1 +\n t/t3403-rebase-skip.sh             |  1 +\n t/t3424-rebase-empty.sh            |  1 +\n t/t3505-cherry-pick-empty.sh       |  1 +\n t/t7505-prepare-commit-msg-hook.sh |  1 +\n 16 files changed, 59 insertions(+), 37 deletions(-)\n\ndiff --git a/builtin/am.c b/builtin/am.c\nindex 4ba44e2d70..faccc45b13 100644\n--- a/builtin/am.c\n+++ b/builtin/am.c\n@@ -1718,6 +1718,7 @@ static void do_commit(const struct am_state *state)\n \n \trun_hooks(\"post-applypatch\");\n \n+\tfree_commit_list(parents);\n \tstrbuf_release(&sb);\n }\n \ndiff --git a/builtin/commit-tree.c b/builtin/commit-tree.c\nindex 1bb7819839..84bb450222 100644\n--- a/builtin/commit-tree.c\n+++ b/builtin/commit-tree.c\n@@ -111,6 +111,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \t\t\tN_(\"GPG sign commit\"), PARSE_OPT_OPTARG, NULL, (intptr_t) \"\" },\n \t\tOPT_END()\n \t};\n+\tint ret;\n \n \tgit_config(git_default_config, NULL);\n \n@@ -132,11 +133,15 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix)\n \n \tif (commit_tree(buffer.buf, buffer.len, &tree_oid, parents, &commit_oid,\n \t\t\tNULL, sign_commit)) {\n-\t\tstrbuf_release(&buffer);\n-\t\treturn 1;\n+\t\tret = 1;\n+\t\tgoto out;\n \t}\n \n \tprintf(\"%s\\n\", oid_to_hex(&commit_oid));\n+\tret = 0;\n+\n+out:\n+\tfree_commit_list(parents);\n \tstrbuf_release(&buffer);\n-\treturn 0;\n+\treturn ret;\n }\ndiff --git a/builtin/commit.c b/builtin/commit.c\nindex dcaf4efa03..d5713455e5 100644\n--- a/builtin/commit.c\n+++ b/builtin/commit.c\n@@ -1848,7 +1848,6 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \t\trollback_index_files();\n \t\tdie(_(\"failed to write commit object\"));\n \t}\n-\tfree_commit_extra_headers(extra);\n \n \tif (update_head_with_reflog(current_head, &oid, reflog_msg, &sb,\n \t\t\t\t    &err)) {\n@@ -1890,6 +1889,8 @@ int cmd_commit(int argc, const char **argv, const char *prefix)\n \tapply_autostash_ref(the_repository, \"MERGE_AUTOSTASH\");\n \n cleanup:\n+\tfree_commit_extra_headers(extra);\n+\tfree_commit_list(parents);\n \tstrbuf_release(&author_ident);\n \tstrbuf_release(&err);\n \tstrbuf_release(&sb);\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex daed2d4e1e..50b0c87a95 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -895,7 +895,7 @@ static void prepare_to_commit(struct commit_list *remoteheads)\n static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n {\n \tstruct object_id result_tree, result_commit;\n-\tstruct commit_list *parents, **pptr = &parents;\n+\tstruct commit_list *parents = NULL, **pptr = &parents;\n \n \tif (repo_refresh_and_write_index(the_repository, REFRESH_QUIET,\n \t\t\t\t\t SKIP_IF_UNCHANGED, 0, NULL, NULL,\n@@ -911,7 +911,9 @@ static int merge_trivial(struct commit *head, struct commit_list *remoteheads)\n \t\t\t&result_commit, NULL, sign_commit))\n \t\tdie(_(\"failed to write commit object\"));\n \tfinish(head, remoteheads, &result_commit, \"In-index merge\");\n+\n \tremove_merge_branch_state(the_repository);\n+\tfree_commit_list(parents);\n \treturn 0;\n }\n \n@@ -937,8 +939,10 @@ static int finish_automerge(struct commit *head,\n \t\tdie(_(\"failed to write commit object\"));\n \tstrbuf_addf(&buf, \"Merge made by the '%s' strategy.\", wt_strategy);\n \tfinish(head, remoteheads, &result_commit, buf.buf);\n+\n \tstrbuf_release(&buf);\n \tremove_merge_branch_state(the_repository);\n+\tfree_commit_list(parents);\n \treturn 0;\n }\n \ndiff --git a/builtin/replay.c b/builtin/replay.c\nindex 6bf0691f15..0448326636 100644\n--- a/builtin/replay.c\n+++ b/builtin/replay.c\n@@ -52,11 +52,11 @@ static struct commit *create_commit(struct tree *tree,\n \t\t\t\t    struct commit *parent)\n {\n \tstruct object_id ret;\n-\tstruct object *obj;\n+\tstruct object *obj = NULL;\n \tstruct commit_list *parents = NULL;\n \tchar *author;\n \tchar *sign_commit = NULL; /* FIXME: cli users might want to sign again */\n-\tstruct commit_extra_header *extra;\n+\tstruct commit_extra_header *extra = NULL;\n \tstruct strbuf msg = STRBUF_INIT;\n \tconst char *out_enc = get_commit_output_encoding();\n \tconst char *message = repo_logmsg_reencode(the_repository, based_on,\n@@ -73,12 +73,16 @@ static struct commit *create_commit(struct tree *tree,\n \tif (commit_tree_extended(msg.buf, msg.len, &tree->object.oid, parents,\n \t\t\t\t &ret, author, NULL, sign_commit, extra)) {\n \t\terror(_(\"failed to write commit object\"));\n-\t\treturn NULL;\n+\t\tgoto out;\n \t}\n-\tfree(author);\n-\tstrbuf_release(&msg);\n \n \tobj = parse_object(the_repository, &ret);\n+\n+out:\n+\tfree_commit_extra_headers(extra);\n+\tfree_commit_list(parents);\n+\tstrbuf_release(&msg);\n+\tfree(author);\n \treturn (struct commit *)obj;\n }\n \ndiff --git a/builtin/stash.c b/builtin/stash.c\nindex 1ed0a9a5d9..46b981c4dd 100644\n--- a/builtin/stash.c\n+++ b/builtin/stash.c\n@@ -1416,6 +1416,9 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \t\tgoto done;\n \t}\n \n+\tfree_commit_list(parents);\n+\tparents = NULL;\n+\n \tif (include_untracked) {\n \t\tif (save_untracked_files(info, &msg, untracked_files)) {\n \t\t\tif (!quiet)\n@@ -1461,11 +1464,6 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \telse\n \t\tstrbuf_insertf(stash_msg_buf, 0, \"On %s: \", branch_name);\n \n-\t/*\n-\t * `parents` will be empty after calling `commit_tree()`, so there is\n-\t * no need to call `free_commit_list()`\n-\t */\n-\tparents = NULL;\n \tif (untracked_commit_option)\n \t\tcommit_list_insert(lookup_commit(the_repository,\n \t\t\t\t\t\t &info->u_commit),\n@@ -1487,6 +1485,7 @@ static int do_create_stash(const struct pathspec *ps, struct strbuf *stash_msg_b\n \tstrbuf_release(&commit_tree_label);\n \tstrbuf_release(&msg);\n \tstrbuf_release(&untracked_files);\n+\tfree_commit_list(parents);\n \treturn ret;\n }\n \ndiff --git a/commit.c b/commit.c\nindex 1d08951007..f674eca320 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -1262,7 +1262,7 @@ int remove_signature(struct strbuf *buf)\n \treturn sigs[0].start != NULL;\n }\n \n-static void handle_signed_tag(struct commit *parent, struct commit_extra_header ***tail)\n+static void handle_signed_tag(const struct commit *parent, struct commit_extra_header ***tail)\n {\n \tstruct merge_remote_desc *desc;\n \tstruct commit_extra_header *mergetag;\n@@ -1359,17 +1359,17 @@ void verify_merge_signature(struct commit *commit, int verbosity,\n \tsignature_check_clear(&signature_check);\n }\n \n-void append_merge_tag_headers(struct commit_list *parents,\n+void append_merge_tag_headers(const struct commit_list *parents,\n \t\t\t      struct commit_extra_header ***tail)\n {\n \twhile (parents) {\n-\t\tstruct commit *parent = parents->item;\n+\t\tconst struct commit *parent = parents->item;\n \t\thandle_signed_tag(parent, tail);\n \t\tparents = parents->next;\n \t}\n }\n \n-static int convert_commit_extra_headers(struct commit_extra_header *orig,\n+static int convert_commit_extra_headers(const struct commit_extra_header *orig,\n \t\t\t\t\tstruct commit_extra_header **result)\n {\n \tconst struct git_hash_algo *compat = the_repository->compat_hash_algo;\n@@ -1403,7 +1403,7 @@ static int convert_commit_extra_headers(struct commit_extra_header *orig,\n }\n \n static void add_extra_header(struct strbuf *buffer,\n-\t\t\t     struct commit_extra_header *extra)\n+\t\t\t     const struct commit_extra_header *extra)\n {\n \tstrbuf_addstr(buffer, extra->key);\n \tif (extra->len)\n@@ -1517,7 +1517,7 @@ void free_commit_extra_headers(struct commit_extra_header *extra)\n }\n \n int commit_tree(const char *msg, size_t msg_len, const struct object_id *tree,\n-\t\tstruct commit_list *parents, struct object_id *ret,\n+\t\tconst struct commit_list *parents, struct object_id *ret,\n \t\tconst char *author, const char *sign_commit)\n {\n \tstruct commit_extra_header *extra = NULL, **tail = &extra;\n@@ -1649,7 +1649,7 @@ static void write_commit_tree(struct strbuf *buffer, const char *msg, size_t msg\n \t\t\t      const struct object_id *tree,\n \t\t\t      const struct object_id *parents, size_t parents_len,\n \t\t\t      const char *author, const char *committer,\n-\t\t\t      struct commit_extra_header *extra)\n+\t\t\t      const struct commit_extra_header *extra)\n {\n \tint encoding_is_utf8;\n \tsize_t i;\n@@ -1690,10 +1690,10 @@ static void write_commit_tree(struct strbuf *buffer, const char *msg, size_t msg\n \n int commit_tree_extended(const char *msg, size_t msg_len,\n \t\t\t const struct object_id *tree,\n-\t\t\t struct commit_list *parents, struct object_id *ret,\n+\t\t\t const struct commit_list *parents, struct object_id *ret,\n \t\t\t const char *author, const char *committer,\n \t\t\t const char *sign_commit,\n-\t\t\t struct commit_extra_header *extra)\n+\t\t\t const struct commit_extra_header *extra)\n {\n \tstruct repository *r = the_repository;\n \tint result = 0;\n@@ -1715,10 +1715,8 @@ int commit_tree_extended(const char *msg, size_t msg_len,\n \tnparents = commit_list_count(parents);\n \tCALLOC_ARRAY(parent_buf, nparents);\n \ti = 0;\n-\twhile (parents) {\n-\t\tstruct commit *parent = pop_commit(&parents);\n-\t\toidcpy(&parent_buf[i++], &parent->object.oid);\n-\t}\n+\tfor (const struct commit_list *p = parents; p; p = p->next)\n+\t\toidcpy(&parent_buf[i++], &p->item->object.oid);\n \n \twrite_commit_tree(&buffer, msg, msg_len, tree, parent_buf, nparents, author, committer, extra);\n \tif (sign_commit && sign_commit_to_strbuf(&sig, &buffer, sign_commit)) {\n@@ -1814,7 +1812,7 @@ int commit_tree_extended(const char *msg, size_t msg_len,\n define_commit_slab(merge_desc_slab, struct merge_remote_desc *);\n static struct merge_desc_slab merge_desc_slab = COMMIT_SLAB_INIT(1, merge_desc_slab);\n \n-struct merge_remote_desc *merge_remote_util(struct commit *commit)\n+struct merge_remote_desc *merge_remote_util(const struct commit *commit)\n {\n \treturn *merge_desc_slab_at(&merge_desc_slab, commit);\n }\ndiff --git a/commit.h b/commit.h\nindex 62fe0d77a7..442e50ff24 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -260,19 +260,19 @@ struct commit_extra_header {\n \tsize_t len;\n };\n \n-void append_merge_tag_headers(struct commit_list *parents,\n+void append_merge_tag_headers(const struct commit_list *parents,\n \t\t\t      struct commit_extra_header ***tail);\n \n int commit_tree(const char *msg, size_t msg_len,\n \t\tconst struct object_id *tree,\n-\t\tstruct commit_list *parents, struct object_id *ret,\n+\t\tconst struct commit_list *parents, struct object_id *ret,\n \t\tconst char *author, const char *sign_commit);\n \n int commit_tree_extended(const char *msg, size_t msg_len,\n \t\t\t const struct object_id *tree,\n-\t\t\t struct commit_list *parents, struct object_id *ret,\n+\t\t\t const struct commit_list *parents, struct object_id *ret,\n \t\t\t const char *author, const char *committer,\n-\t\t\t const char *sign_commit, struct commit_extra_header *);\n+\t\t\t const char *sign_commit, const struct commit_extra_header *);\n \n struct commit_extra_header *read_commit_extra_headers(struct commit *, const char **);\n \n@@ -306,7 +306,7 @@ struct merge_remote_desc {\n \tstruct object *obj; /* the named object, could be a tag */\n \tchar name[FLEX_ARRAY];\n };\n-struct merge_remote_desc *merge_remote_util(struct commit *);\n+struct merge_remote_desc *merge_remote_util(const struct commit *);\n void set_merge_remote_desc(struct commit *commit,\n \t\t\t   const char *name, struct object *obj);\n \ndiff --git a/notes-merge.c b/notes-merge.c\nindex 6a9a139b12..f3cc84f45d 100644\n--- a/notes-merge.c\n+++ b/notes-merge.c\n@@ -661,6 +661,7 @@ int notes_merge(struct notes_merge_options *o,\n \t\tcommit_list_insert(local, &parents);\n \t\tcreate_notes_commit(o->repo, local_tree, parents, o->commit_msg.buf,\n \t\t\t\t    o->commit_msg.len, result_oid);\n+\t\tfree_commit_list(parents);\n \t}\n \n found_result:\ndiff --git a/notes-utils.c b/notes-utils.c\nindex 671d1969b1..3198c14e4d 100644\n--- a/notes-utils.c\n+++ b/notes-utils.c\n@@ -9,10 +9,11 @@\n \n void create_notes_commit(struct repository *r,\n \t\t\t struct notes_tree *t,\n-\t\t\t struct commit_list *parents,\n+\t\t\t const struct commit_list *parents,\n \t\t\t const char *msg, size_t msg_len,\n \t\t\t struct object_id *result_oid)\n {\n+\tstruct commit_list *parents_to_free = NULL;\n \tstruct object_id tree_oid;\n \n \tassert(t->initialized);\n@@ -27,7 +28,8 @@ void create_notes_commit(struct repository *r,\n \t\t\tstruct commit *parent = lookup_commit(r, &parent_oid);\n \t\t\tif (repo_parse_commit(r, parent))\n \t\t\t\tdie(\"Failed to find/parse commit %s\", t->ref);\n-\t\t\tcommit_list_insert(parent, &parents);\n+\t\t\tcommit_list_insert(parent, &parents_to_free);\n+\t\t\tparents = parents_to_free;\n \t\t}\n \t\t/* else: t->ref points to nothing, assume root/orphan commit */\n \t}\n@@ -35,6 +37,8 @@ void create_notes_commit(struct repository *r,\n \tif (commit_tree(msg, msg_len, &tree_oid, parents, result_oid, NULL,\n \t\t\tNULL))\n \t\tdie(\"Failed to commit notes tree to database\");\n+\n+\tfree_commit_list(parents_to_free);\n }\n \n void commit_notes(struct repository *r, struct notes_tree *t, const char *msg)\ndiff --git a/notes-utils.h b/notes-utils.h\nindex d9b3c09eaf..c54b1fe141 100644\n--- a/notes-utils.h\n+++ b/notes-utils.h\n@@ -20,7 +20,7 @@ struct repository;\n  */\n void create_notes_commit(struct repository *r,\n \t\t\t struct notes_tree *t,\n-\t\t\t struct commit_list *parents,\n+\t\t\t const struct commit_list *parents,\n \t\t\t const char *msg, size_t msg_len,\n \t\t\t struct object_id *result_oid);\n \ndiff --git a/sequencer.c b/sequencer.c\nindex 30513e87bf..475646671a 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -1711,6 +1711,7 @@ static int try_to_commit(struct repository *r,\n \n out:\n \tfree_commit_extra_headers(extra);\n+\tfree_commit_list(parents);\n \tstrbuf_release(&err);\n \tstrbuf_release(&commit_msg);\n \tfree(amend_author);\ndiff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh\nindex a1911c4a9d..4f1d6e8ea6 100755\n--- a/t/t3403-rebase-skip.sh\n+++ b/t/t3403-rebase-skip.sh\n@@ -8,6 +8,7 @@ test_description='git rebase --merge --skip tests'\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3424-rebase-empty.sh b/t/t3424-rebase-empty.sh\nindex 1ee6b00fd5..515c949ae3 100755\n--- a/t/t3424-rebase-empty.sh\n+++ b/t/t3424-rebase-empty.sh\n@@ -2,6 +2,7 @@\n \n test_description='git rebase of commits that start or become empty'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup test repository' '\ndiff --git a/t/t3505-cherry-pick-empty.sh b/t/t3505-cherry-pick-empty.sh\nindex 9748443530..ead3fb4680 100755\n--- a/t/t3505-cherry-pick-empty.sh\n+++ b/t/t3505-cherry-pick-empty.sh\n@@ -5,6 +5,7 @@ test_description='test cherry-picking an empty commit'\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 test_expect_success setup '\ndiff --git a/t/t7505-prepare-commit-msg-hook.sh b/t/t7505-prepare-commit-msg-hook.sh\nindex 2128142a61..b88383df9e 100755\n--- a/t/t7505-prepare-commit-msg-hook.sh\n+++ b/t/t7505-prepare-commit-msg-hook.sh\n@@ -5,6 +5,7 @@ test_description='prepare-commit-msg hook'\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 test_expect_success 'set up commits for rebasing' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496880","messageId":"144eb23617f679034581ec7ffefde8a803499d3b.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 20/29] sequencer: fix leaking string buffer in `commit_staged_changes()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:47Z","receivedAt":"2024-06-11T09:20:52Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"We're leaking the `rev` string buffer in various call paths. Refactor\nthe function to have a common exit path so that we can release its\nmemory reliably.\n\nThis fixes a subset of tests failing with the memory sanitizer in t3404.\nBut as there are more failures, we cannot yet mark the whole test suite\nas passing.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c | 111 ++++++++++++++++++++++++++++++++++------------------\n 1 file changed, 73 insertions(+), 38 deletions(-)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex 475646671a..c581061b6d 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5146,33 +5146,47 @@ static int commit_staged_changes(struct repository *r,\n \tstruct replay_ctx *ctx = opts->ctx;\n \tunsigned int flags = ALLOW_EMPTY | EDIT_MSG;\n \tunsigned int final_fixup = 0, is_clean;\n+\tstruct strbuf rev = STRBUF_INIT;\n+\tint ret;\n \n-\tif (has_unstaged_changes(r, 1))\n-\t\treturn error(_(\"cannot rebase: You have unstaged changes.\"));\n+\tif (has_unstaged_changes(r, 1)) {\n+\t\tret = error(_(\"cannot rebase: You have unstaged changes.\"));\n+\t\tgoto out;\n+\t}\n \n \tis_clean = !has_uncommitted_changes(r, 0);\n \n \tif (!is_clean && !file_exists(rebase_path_message())) {\n \t\tconst char *gpg_opt = gpg_sign_opt_quoted(opts);\n-\n-\t\treturn error(_(staged_changes_advice), gpg_opt, gpg_opt);\n+\t\tret = error(_(staged_changes_advice), gpg_opt, gpg_opt);\n+\t\tgoto out;\n \t}\n+\n \tif (file_exists(rebase_path_amend())) {\n-\t\tstruct strbuf rev = STRBUF_INIT;\n \t\tstruct object_id head, to_amend;\n \n-\t\tif (repo_get_oid(r, \"HEAD\", &head))\n-\t\t\treturn error(_(\"cannot amend non-existing commit\"));\n-\t\tif (!read_oneliner(&rev, rebase_path_amend(), 0))\n-\t\t\treturn error(_(\"invalid file: '%s'\"), rebase_path_amend());\n-\t\tif (get_oid_hex(rev.buf, &to_amend))\n-\t\t\treturn error(_(\"invalid contents: '%s'\"),\n-\t\t\t\trebase_path_amend());\n-\t\tif (!is_clean && !oideq(&head, &to_amend))\n-\t\t\treturn error(_(\"\\nYou have uncommitted changes in your \"\n-\t\t\t\t       \"working tree. Please, commit them\\n\"\n-\t\t\t\t       \"first and then run 'git rebase \"\n-\t\t\t\t       \"--continue' again.\"));\n+\t\tif (repo_get_oid(r, \"HEAD\", &head)) {\n+\t\t\tret = error(_(\"cannot amend non-existing commit\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!read_oneliner(&rev, rebase_path_amend(), 0)) {\n+\t\t\tret = error(_(\"invalid file: '%s'\"), rebase_path_amend());\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (get_oid_hex(rev.buf, &to_amend)) {\n+\t\t\tret = error(_(\"invalid contents: '%s'\"),\n+\t\t\t\t    rebase_path_amend());\n+\t\t\tgoto out;\n+\t\t}\n+\t\tif (!is_clean && !oideq(&head, &to_amend)) {\n+\t\t\tret = error(_(\"\\nYou have uncommitted changes in your \"\n+\t\t\t\t      \"working tree. Please, commit them\\n\"\n+\t\t\t\t      \"first and then run 'git rebase \"\n+\t\t\t\t      \"--continue' again.\"));\n+\t\t\tgoto out;\n+\t\t}\n \t\t/*\n \t\t * When skipping a failed fixup/squash, we need to edit the\n \t\t * commit message, the current fixup list and count, and if it\n@@ -5204,9 +5218,11 @@ static int commit_staged_changes(struct repository *r,\n \t\t\t\tlen--;\n \t\t\tstrbuf_setlen(&ctx->current_fixups, len);\n \t\t\tif (write_message(p, len, rebase_path_current_fixups(),\n-\t\t\t\t\t  0) < 0)\n-\t\t\t\treturn error(_(\"could not write file: '%s'\"),\n-\t\t\t\t\t     rebase_path_current_fixups());\n+\t\t\t\t\t  0) < 0) {\n+\t\t\t\tret = error(_(\"could not write file: '%s'\"),\n+\t\t\t\t\t    rebase_path_current_fixups());\n+\t\t\t\tgoto out;\n+\t\t\t}\n \n \t\t\t/*\n \t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n@@ -5236,35 +5252,38 @@ static int commit_staged_changes(struct repository *r,\n \t\t\t\t * We need to update the squash message to skip\n \t\t\t\t * the latest commit message.\n \t\t\t\t */\n-\t\t\t\tint res = 0;\n \t\t\t\tstruct commit *commit;\n \t\t\t\tconst char *msg;\n \t\t\t\tconst char *path = rebase_path_squash_msg();\n \t\t\t\tconst char *encoding = get_commit_output_encoding();\n \n-\t\t\t\tif (parse_head(r, &commit))\n-\t\t\t\t\treturn error(_(\"could not parse HEAD\"));\n+\t\t\t\tif (parse_head(r, &commit)) {\n+\t\t\t\t\tret = error(_(\"could not parse HEAD\"));\n+\t\t\t\t\tgoto out;\n+\t\t\t\t}\n \n \t\t\t\tp = repo_logmsg_reencode(r, commit, NULL, encoding);\n \t\t\t\tif (!p)  {\n-\t\t\t\t\tres = error(_(\"could not parse commit %s\"),\n+\t\t\t\t\tret = error(_(\"could not parse commit %s\"),\n \t\t\t\t\t\t    oid_to_hex(&commit->object.oid));\n \t\t\t\t\tgoto unuse_commit_buffer;\n \t\t\t\t}\n \t\t\t\tfind_commit_subject(p, &msg);\n \t\t\t\tif (write_message(msg, strlen(msg), path, 0)) {\n-\t\t\t\t\tres = error(_(\"could not write file: \"\n+\t\t\t\t\tret = error(_(\"could not write file: \"\n \t\t\t\t\t\t       \"'%s'\"), path);\n \t\t\t\t\tgoto unuse_commit_buffer;\n \t\t\t\t}\n+\n+\t\t\t\tret = 0;\n+\n \t\t\tunuse_commit_buffer:\n \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n-\t\t\t\tif (res)\n-\t\t\t\t\treturn res;\n+\t\t\t\tif (ret)\n+\t\t\t\t\tgoto out;\n \t\t\t}\n \t\t}\n \n-\t\tstrbuf_release(&rev);\n \t\tflags |= AMEND_MSG;\n \t}\n \n@@ -5272,18 +5291,29 @@ static int commit_staged_changes(struct repository *r,\n \t\tif (refs_ref_exists(get_main_ref_store(r),\n \t\t\t\t    \"CHERRY_PICK_HEAD\") &&\n \t\t    refs_delete_ref(get_main_ref_store(r), \"\",\n-\t\t\t\t    \"CHERRY_PICK_HEAD\", NULL, REF_NO_DEREF))\n-\t\t\treturn error(_(\"could not remove CHERRY_PICK_HEAD\"));\n-\t\tif (unlink(git_path_merge_msg(r)) && errno != ENOENT)\n-\t\t\treturn error_errno(_(\"could not remove '%s'\"),\n-\t\t\t\t\t   git_path_merge_msg(r));\n-\t\tif (!final_fixup)\n-\t\t\treturn 0;\n+\t\t\t\t    \"CHERRY_PICK_HEAD\", NULL, REF_NO_DEREF)) {\n+\t\t\tret = error(_(\"could not remove CHERRY_PICK_HEAD\"));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (unlink(git_path_merge_msg(r)) && errno != ENOENT) {\n+\t\t\tret = error_errno(_(\"could not remove '%s'\"),\n+\t\t\t\t\t  git_path_merge_msg(r));\n+\t\t\tgoto out;\n+\t\t}\n+\n+\t\tif (!final_fixup) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \tif (run_git_commit(final_fixup ? NULL : rebase_path_message(),\n-\t\t\t   opts, flags))\n-\t\treturn error(_(\"could not commit staged changes.\"));\n+\t\t\t   opts, flags)) {\n+\t\tret = error(_(\"could not commit staged changes.\"));\n+\t\tgoto out;\n+\t}\n+\n \tunlink(rebase_path_amend());\n \tunlink(git_path_merge_head(r));\n \trefs_delete_ref(get_main_ref_store(r), \"\", \"AUTO_MERGE\",\n@@ -5301,7 +5331,12 @@ static int commit_staged_changes(struct repository *r,\n \t\tstrbuf_reset(&ctx->current_fixups);\n \t\tctx->current_fixup_count = 0;\n \t}\n-\treturn 0;\n+\n+\tret = 0;\n+\n+out:\n+\tstrbuf_release(&rev);\n+\treturn ret;\n }\n \n int sequencer_continue(struct repository *r, struct replay_opts *opts)\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496881","messageId":"8e1cf8a18b9926db801d9a44afe8d4edeb2401a0.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 21/29] apply: fix leaking string in `match_fragment()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:52Z","receivedAt":"2024-06-11T09:20:57Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Before calling `update_pre_post_images()`, we call `strbuf_detach()` to\nput its buffer into a new string variable that we then pass to that\nfunction. Besides being rather pointless, it also causes us to leak\nmemory of that variable because we never free it.\n\nGet rid of the variable altogether and instead reach into the `strbuf`\ndirectly. While at it, refactor the code to have a common exit path and\nmark string that do not contain allocated memory as constant.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n apply.c                          | 87 ++++++++++++++++++++------------\n t/t3417-rebase-whitespace-fix.sh |  1 +\n 2 files changed, 56 insertions(+), 32 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex d8d26a48f1..fd7e3d649f 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -2494,18 +2494,21 @@ static int match_fragment(struct apply_state *state,\n \t\t\t  int match_beginning, int match_end)\n {\n \tint i;\n-\tchar *fixed_buf, *buf, *orig, *target;\n-\tstruct strbuf fixed;\n-\tsize_t fixed_len, postlen;\n+\tconst char *orig, *target;\n+\tstruct strbuf fixed = STRBUF_INIT;\n+\tsize_t postlen;\n \tint preimage_limit;\n+\tint ret;\n \n \tif (preimage->nr + current_lno <= img->nr) {\n \t\t/*\n \t\t * The hunk falls within the boundaries of img.\n \t\t */\n \t\tpreimage_limit = preimage->nr;\n-\t\tif (match_end && (preimage->nr + current_lno != img->nr))\n-\t\t\treturn 0;\n+\t\tif (match_end && (preimage->nr + current_lno != img->nr)) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t} else if (state->ws_error_action == correct_ws_error &&\n \t\t   (ws_rule & WS_BLANK_AT_EOF)) {\n \t\t/*\n@@ -2522,17 +2525,23 @@ static int match_fragment(struct apply_state *state,\n \t\t * we are not removing blanks at the end, so we\n \t\t * should reject the hunk at this position.\n \t\t */\n-\t\treturn 0;\n+\t\tret = 0;\n+\t\tgoto out;\n \t}\n \n-\tif (match_beginning && current_lno)\n-\t\treturn 0;\n+\tif (match_beginning && current_lno) {\n+\t\tret = 0;\n+\t\tgoto out;\n+\t}\n \n \t/* Quick hash check */\n-\tfor (i = 0; i < preimage_limit; i++)\n+\tfor (i = 0; i < preimage_limit; i++) {\n \t\tif ((img->line[current_lno + i].flag & LINE_PATCHED) ||\n-\t\t    (preimage->line[i].hash != img->line[current_lno + i].hash))\n-\t\t\treturn 0;\n+\t\t    (preimage->line[i].hash != img->line[current_lno + i].hash)) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n+\t}\n \n \tif (preimage_limit == preimage->nr) {\n \t\t/*\n@@ -2545,8 +2554,10 @@ static int match_fragment(struct apply_state *state,\n \t\tif ((match_end\n \t\t     ? (current + preimage->len == img->len)\n \t\t     : (current + preimage->len <= img->len)) &&\n-\t\t    !memcmp(img->buf + current, preimage->buf, preimage->len))\n-\t\t\treturn 1;\n+\t\t    !memcmp(img->buf + current, preimage->buf, preimage->len)) {\n+\t\t\tret = 1;\n+\t\t\tgoto out;\n+\t\t}\n \t} else {\n \t\t/*\n \t\t * The preimage extends beyond the end of img, so\n@@ -2555,7 +2566,7 @@ static int match_fragment(struct apply_state *state,\n \t\t * There must be one non-blank context line that match\n \t\t * a line before the end of img.\n \t\t */\n-\t\tchar *buf_end;\n+\t\tconst char *buf, *buf_end;\n \n \t\tbuf = preimage->buf;\n \t\tbuf_end = buf;\n@@ -2565,8 +2576,10 @@ static int match_fragment(struct apply_state *state,\n \t\tfor ( ; buf < buf_end; buf++)\n \t\t\tif (!isspace(*buf))\n \t\t\t\tbreak;\n-\t\tif (buf == buf_end)\n-\t\t\treturn 0;\n+\t\tif (buf == buf_end) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -2574,12 +2587,16 @@ static int match_fragment(struct apply_state *state,\n \t * fuzzy matching. We collect all the line length information because\n \t * we need it to adjust whitespace if we match.\n \t */\n-\tif (state->ws_ignore_action == ignore_ws_change)\n-\t\treturn line_by_line_fuzzy_match(img, preimage, postimage,\n-\t\t\t\t\t\tcurrent, current_lno, preimage_limit);\n+\tif (state->ws_ignore_action == ignore_ws_change) {\n+\t\tret = line_by_line_fuzzy_match(img, preimage, postimage,\n+\t\t\t\t\t       current, current_lno, preimage_limit);\n+\t\tgoto out;\n+\t}\n \n-\tif (state->ws_error_action != correct_ws_error)\n-\t\treturn 0;\n+\tif (state->ws_error_action != correct_ws_error) {\n+\t\tret = 0;\n+\t\tgoto out;\n+\t}\n \n \t/*\n \t * The hunk does not apply byte-by-byte, but the hash says\n@@ -2608,7 +2625,7 @@ static int match_fragment(struct apply_state *state,\n \t * but in this loop we will only handle the part of the\n \t * preimage that falls within the file.\n \t */\n-\tstrbuf_init(&fixed, preimage->len + 1);\n+\tstrbuf_grow(&fixed, preimage->len + 1);\n \torig = preimage->buf;\n \ttarget = img->buf + current;\n \tfor (i = 0; i < preimage_limit; i++) {\n@@ -2644,8 +2661,10 @@ static int match_fragment(struct apply_state *state,\n \t\t\tpostlen += tgtfix.len;\n \n \t\tstrbuf_release(&tgtfix);\n-\t\tif (!match)\n-\t\t\tgoto unmatch_exit;\n+\t\tif (!match) {\n+\t\t\tret = 0;\n+\t\t\tgoto out;\n+\t\t}\n \n \t\torig += oldlen;\n \t\ttarget += tgtlen;\n@@ -2666,9 +2685,13 @@ static int match_fragment(struct apply_state *state,\n \t\t/* Try fixing the line in the preimage */\n \t\tws_fix_copy(&fixed, orig, oldlen, ws_rule, NULL);\n \n-\t\tfor (j = fixstart; j < fixed.len; j++)\n-\t\t\tif (!isspace(fixed.buf[j]))\n-\t\t\t\tgoto unmatch_exit;\n+\t\tfor (j = fixstart; j < fixed.len; j++) {\n+\t\t\tif (!isspace(fixed.buf[j])) {\n+\t\t\t\tret = 0;\n+\t\t\t\tgoto out;\n+\t\t\t}\n+\t\t}\n+\n \n \t\torig += oldlen;\n \t}\n@@ -2678,16 +2701,16 @@ static int match_fragment(struct apply_state *state,\n \t * has whitespace breakages unfixed, and fixing them makes the\n \t * hunk match.  Update the context lines in the postimage.\n \t */\n-\tfixed_buf = strbuf_detach(&fixed, &fixed_len);\n \tif (postlen < postimage->len)\n \t\tpostlen = 0;\n \tupdate_pre_post_images(preimage, postimage,\n-\t\t\t       fixed_buf, fixed_len, postlen);\n-\treturn 1;\n+\t\t\t       fixed.buf, fixed.len, postlen);\n \n- unmatch_exit:\n+\tret = 1;\n+\n+out:\n \tstrbuf_release(&fixed);\n-\treturn 0;\n+\treturn ret;\n }\n \n static int find_pos(struct apply_state *state,\ndiff --git a/t/t3417-rebase-whitespace-fix.sh b/t/t3417-rebase-whitespace-fix.sh\nindex 96f2cf22fa..22ee3a2045 100755\n--- a/t/t3417-rebase-whitespace-fix.sh\n+++ b/t/t3417-rebase-whitespace-fix.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --whitespace=fix\n This test runs git rebase --whitespace=fix and make sure that it works.\n '\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # prepare initial revision of \"file\" with a blank line at the end\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496882","messageId":"8090891bd048234b34dbe9c8384a815540e3d45d.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 22/29] builtin/clone: plug leaking HEAD ref in `wanted_peer_refs()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:20:57Z","receivedAt":"2024-06-11T09:21:02Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `wanted_peer_refs()` we first create a copy of the \"HEAD\" ref. This\ncopy may not actually be passed back to the caller, but is not getting\nfreed in this case. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/clone.c          | 3 ++-\n t/t5300-pack-object.sh   | 4 ++--\n t/t5305-include-tag.sh   | 1 +\n t/t5612-clone-refspec.sh | 1 +\n 4 files changed, 6 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin/clone.c b/builtin/clone.c\nindex 730b3efae6..ae9863ed47 100644\n--- a/builtin/clone.c\n+++ b/builtin/clone.c\n@@ -530,7 +530,8 @@ static struct ref *wanted_peer_refs(const struct ref *refs,\n \t\tif (!option_branch)\n \t\t\tremote_head = guess_remote_head(head, refs, 0);\n \t\telse {\n-\t\t\tlocal_refs = NULL;\n+\t\t\tfree_one_ref(head);\n+\t\t\tlocal_refs = head = NULL;\n \t\t\ttail = &local_refs;\n \t\t\tremote_head = copy_ref(find_remote_branch(refs, option_branch));\n \t\t}\ndiff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh\nindex 61e2be2903..4ad023c846 100755\n--- a/t/t5300-pack-object.sh\n+++ b/t/t5300-pack-object.sh\n@@ -3,9 +3,9 @@\n # Copyright (c) 2005 Junio C Hamano\n #\n \n-test_description='git pack-object\n+test_description='git pack-object'\n \n-'\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\ndiff --git a/t/t5305-include-tag.sh b/t/t5305-include-tag.sh\nindex 44bd9ef45f..dc8fe55c82 100755\n--- a/t/t5305-include-tag.sh\n+++ b/t/t5305-include-tag.sh\n@@ -4,6 +4,7 @@ test_description='git pack-object --include-tag'\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 TRASH=$(pwd)\ndiff --git a/t/t5612-clone-refspec.sh b/t/t5612-clone-refspec.sh\nindex 3126cfd7e9..72762de977 100755\n--- a/t/t5612-clone-refspec.sh\n+++ b/t/t5612-clone-refspec.sh\n@@ -4,6 +4,7 @@ test_description='test refspec written by clone-command'\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 test_expect_success 'setup' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496883","messageId":"1cdd988c4521bd79a1303b6d455cb5a66680e45a.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 23/29] sequencer: fix memory leaks in `make_script_with_merges()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:21:01Z","receivedAt":"2024-06-11T09:21:06Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"Fix some trivial memory leaks in `make_script_with_merges()`.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n sequencer.c                       | 3 +++\n t/t3418-rebase-continue.sh        | 1 +\n t/t3421-rebase-topology-linear.sh | 2 ++\n t/t3434-rebase-i18n.sh            | 1 +\n 4 files changed, 7 insertions(+)\n\ndiff --git a/sequencer.c b/sequencer.c\nindex c581061b6d..20807ea7e5 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -5951,6 +5951,9 @@ static int make_script_with_merges(struct pretty_print_context *pp,\n \tstrbuf_release(&oneline);\n \tstrbuf_release(&buf);\n \n+\toidset_clear(&interesting);\n+\toidset_clear(&child_seen);\n+\toidset_clear(&shown);\n \toidmap_free(&commit2todo, 1);\n \toidmap_free(&state.commit2label, 1);\n \thashmap_clear_and_free(&state.labels, struct labels_entry, entry);\ndiff --git a/t/t3418-rebase-continue.sh b/t/t3418-rebase-continue.sh\nindex 127216f722..c0d29c2154 100755\n--- a/t/t3418-rebase-continue.sh\n+++ b/t/t3418-rebase-continue.sh\n@@ -5,6 +5,7 @@ test_description='git rebase --continue tests'\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\ndiff --git a/t/t3421-rebase-topology-linear.sh b/t/t3421-rebase-topology-linear.sh\nindex 62d86d557d..737af80bb3 100755\n--- a/t/t3421-rebase-topology-linear.sh\n+++ b/t/t3421-rebase-topology-linear.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='basic rebase topology tests'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n . \"$TEST_DIRECTORY\"/lib-rebase.sh\n \ndiff --git a/t/t3434-rebase-i18n.sh b/t/t3434-rebase-i18n.sh\nindex a4e482d2cd..26a48d6b10 100755\n--- a/t/t3434-rebase-i18n.sh\n+++ b/t/t3434-rebase-i18n.sh\n@@ -17,6 +17,7 @@ Initial setup:\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 compare_msg () {\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496884","messageId":"be9bccc853c690b047fbef6dad419cc35cbbd4c0.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 24/29] builtin/merge: fix leaking `struct cmdnames` in `get_strategy()`","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:21:06Z","receivedAt":"2024-06-11T09:21:10Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In \"builtin/merge.c\" we use the helper infrastructure to figure out what\nmerge strategies there are. We never free contents of the `cmdnames`\nstructures though and thus leak their memory.\n\nFix this by exposing the already existing `clean_cmdnames()` function to\nrelease their memory. As this name isn't quite idiomatic, rename it to\n`cmdnames_release()` while at it.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge.c         | 10 +++++++---\n help.c                  | 12 ++++++------\n help.h                  |  2 ++\n t/t7606-merge-custom.sh |  1 +\n 4 files changed, 16 insertions(+), 9 deletions(-)\n\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 50b0c87a95..682c6ed868 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -164,7 +164,7 @@ static struct strategy *get_strategy(const char *name)\n {\n \tint i;\n \tstruct strategy *ret;\n-\tstatic struct cmdnames main_cmds, other_cmds;\n+\tstatic struct cmdnames main_cmds = {0}, other_cmds = {0};\n \tstatic int loaded;\n \tchar *default_strategy = getenv(\"GIT_TEST_MERGE_ALGORITHM\");\n \n@@ -182,10 +182,9 @@ static struct strategy *get_strategy(const char *name)\n \t\t\treturn &all_strategy[i];\n \n \tif (!loaded) {\n-\t\tstruct cmdnames not_strategies;\n+\t\tstruct cmdnames not_strategies = {0};\n \t\tloaded = 1;\n \n-\t\tmemset(&not_strategies, 0, sizeof(struct cmdnames));\n \t\tload_command_list(\"git-merge-\", &main_cmds, &other_cmds);\n \t\tfor (i = 0; i < main_cmds.cnt; i++) {\n \t\t\tint j, found = 0;\n@@ -197,6 +196,8 @@ static struct strategy *get_strategy(const char *name)\n \t\t\t\tadd_cmdname(&not_strategies, ent->name, ent->len);\n \t\t}\n \t\texclude_cmds(&main_cmds, &not_strategies);\n+\n+\t\tcmdnames_release(&not_strategies);\n \t}\n \tif (!is_in_cmdlist(&main_cmds, name) && !is_in_cmdlist(&other_cmds, name)) {\n \t\tfprintf(stderr, _(\"Could not find merge strategy '%s'.\\n\"), name);\n@@ -216,6 +217,9 @@ static struct strategy *get_strategy(const char *name)\n \tCALLOC_ARRAY(ret, 1);\n \tret->name = xstrdup(name);\n \tret->attr = NO_TRIVIAL;\n+\n+\tcmdnames_release(&main_cmds);\n+\tcmdnames_release(&other_cmds);\n \treturn ret;\n }\n \ndiff --git a/help.c b/help.c\nindex 1d057aa607..3686285ca3 100644\n--- a/help.c\n+++ b/help.c\n@@ -157,7 +157,7 @@ void add_cmdname(struct cmdnames *cmds, const char *name, int len)\n \tcmds->names[cmds->cnt++] = ent;\n }\n \n-static void clean_cmdnames(struct cmdnames *cmds)\n+void cmdnames_release(struct cmdnames *cmds)\n {\n \tint i;\n \tfor (i = 0; i < cmds->cnt; ++i)\n@@ -359,8 +359,8 @@ void list_all_main_cmds(struct string_list *list)\n \tfor (i = 0; i < main_cmds.cnt; i++)\n \t\tstring_list_append(list, main_cmds.names[i]->name);\n \n-\tclean_cmdnames(&main_cmds);\n-\tclean_cmdnames(&other_cmds);\n+\tcmdnames_release(&main_cmds);\n+\tcmdnames_release(&other_cmds);\n }\n \n void list_all_other_cmds(struct string_list *list)\n@@ -375,8 +375,8 @@ void list_all_other_cmds(struct string_list *list)\n \tfor (i = 0; i < other_cmds.cnt; i++)\n \t\tstring_list_append(list, other_cmds.names[i]->name);\n \n-\tclean_cmdnames(&main_cmds);\n-\tclean_cmdnames(&other_cmds);\n+\tcmdnames_release(&main_cmds);\n+\tcmdnames_release(&other_cmds);\n }\n \n void list_cmds_by_category(struct string_list *list,\n@@ -689,7 +689,7 @@ const char *help_unknown_cmd(const char *cmd)\n \tif (autocorrect && n == 1 && SIMILAR_ENOUGH(best_similarity)) {\n \t\tconst char *assumed = main_cmds.names[0]->name;\n \t\tmain_cmds.names[0] = NULL;\n-\t\tclean_cmdnames(&main_cmds);\n+\t\tcmdnames_release(&main_cmds);\n \t\tfprintf_ln(stderr,\n \t\t\t   _(\"WARNING: You called a Git command named '%s', \"\n \t\t\t     \"which does not exist.\"),\ndiff --git a/help.h b/help.h\nindex af073a7a02..e716ee27ea 100644\n--- a/help.h\n+++ b/help.h\n@@ -13,6 +13,8 @@ struct cmdnames {\n \t} **names;\n };\n \n+void cmdnames_release(struct cmdnames *cmds);\n+\n static inline void mput_char(char c, unsigned int num)\n {\n \twhile (num--)\ndiff --git a/t/t7606-merge-custom.sh b/t/t7606-merge-custom.sh\nindex 81fb7c474c..135cb23085 100755\n--- a/t/t7606-merge-custom.sh\n+++ b/t/t7606-merge-custom.sh\n@@ -14,6 +14,7 @@ Testing a custom strategy.\n * (tag: c0) c0\n \"\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'set up custom strategy' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496885","messageId":"6f6456494cc63b28cf64057ee6555b29ac1147a6.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 25/29] merge: fix leaking merge bases","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:21:11Z","receivedAt":"2024-06-11T09:21:15Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When calling either the recursive or the ORT merge machineries we need\nto provide a list of merge bases. The ownership of that parameter is\nthen implicitly transferred to the callee, which is somewhat fishy.\nFurthermore, that list may leak in some cases where the merge machinery\nruns into an error, thus causing a memory leak.\n\nRefactor the code such that we stop transferring ownership. Instead, the\nmerge machinery will now create its own local copies of the passed in\nlist as required if they need to modify the list. Free the list at the\ncallsites as required.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/merge-tree.c       |  1 +\n builtin/merge.c            |  2 ++\n commit.c                   |  2 +-\n commit.h                   |  2 +-\n log-tree.c                 |  1 +\n merge-ort-wrappers.c       |  2 +-\n merge-ort-wrappers.h       |  2 +-\n merge-ort.c                | 12 ++++++----\n merge-ort.h                |  2 +-\n merge-recursive.c          | 49 +++++++++++++++++++++++---------------\n merge-recursive.h          |  2 +-\n sequencer.c                |  1 +\n t/t3430-rebase-merges.sh   |  1 +\n t/t6402-merge-rename.sh    |  1 +\n t/t6430-merge-recursive.sh |  1 +\n t/t6436-merge-overwrite.sh |  1 +\n t/t7611-merge-abort.sh     |  1 +\n 17 files changed, 54 insertions(+), 29 deletions(-)\n\ndiff --git a/builtin/merge-tree.c b/builtin/merge-tree.c\nindex 1082d919fd..dab2fdc2a6 100644\n--- a/builtin/merge-tree.c\n+++ b/builtin/merge-tree.c\n@@ -482,6 +482,7 @@ static int real_merge(struct merge_tree_options *o,\n \t\t\tdie(_(\"refusing to merge unrelated histories\"));\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t\tmerge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);\n+\t\tfree_commit_list(merge_bases);\n \t}\n \n \tif (result.clean < 0)\ndiff --git a/builtin/merge.c b/builtin/merge.c\nindex 682c6ed868..1120a6e2f8 100644\n--- a/builtin/merge.c\n+++ b/builtin/merge.c\n@@ -746,6 +746,8 @@ static int try_merge_strategy(const char *strategy, struct commit_list *common,\n \t\telse\n \t\t\tclean = merge_recursive(&o, head, remoteheads->item,\n \t\t\t\t\t\treversed, &result);\n+\t\tfree_commit_list(reversed);\n+\n \t\tif (clean < 0) {\n \t\t\trollback_lock_file(&lock);\n \t\t\treturn 2;\ndiff --git a/commit.c b/commit.c\nindex f674eca320..1386b12df1 100644\n--- a/commit.c\n+++ b/commit.c\n@@ -680,7 +680,7 @@ unsigned commit_list_count(const struct commit_list *l)\n \treturn c;\n }\n \n-struct commit_list *copy_commit_list(struct commit_list *list)\n+struct commit_list *copy_commit_list(const struct commit_list *list)\n {\n \tstruct commit_list *head = NULL;\n \tstruct commit_list **pp = &head;\ndiff --git a/commit.h b/commit.h\nindex 442e50ff24..acabb05785 100644\n--- a/commit.h\n+++ b/commit.h\n@@ -181,7 +181,7 @@ struct commit_list *commit_list_insert_by_date(struct commit *item,\n void commit_list_sort_by_date(struct commit_list **list);\n \n /* Shallow copy of the input list */\n-struct commit_list *copy_commit_list(struct commit_list *list);\n+struct commit_list *copy_commit_list(const struct commit_list *list);\n \n /* Modify list in-place to reverse it, returning new head; list will be tail */\n struct commit_list *reverse_commit_list(struct commit_list *list);\ndiff --git a/log-tree.c b/log-tree.c\nindex 41416de4e3..91b96d83a0 100644\n--- a/log-tree.c\n+++ b/log-tree.c\n@@ -1047,6 +1047,7 @@ static int do_remerge_diff(struct rev_info *opt,\n \tlog_tree_diff_flush(opt);\n \n \t/* Cleanup */\n+\tfree_commit_list(bases);\n \tcleanup_additional_headers(&opt->diffopt);\n \tstrbuf_release(&parent1_desc);\n \tstrbuf_release(&parent2_desc);\ndiff --git a/merge-ort-wrappers.c b/merge-ort-wrappers.c\nindex 4acedf3c33..d6f6135996 100644\n--- a/merge-ort-wrappers.c\n+++ b/merge-ort-wrappers.c\n@@ -48,7 +48,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,\n int merge_ort_recursive(struct merge_options *opt,\n \t\t\tstruct commit *side1,\n \t\t\tstruct commit *side2,\n-\t\t\tstruct commit_list *merge_bases,\n+\t\t\tconst struct commit_list *merge_bases,\n \t\t\tstruct commit **result)\n {\n \tstruct tree *head = repo_get_commit_tree(opt->repo, side1);\ndiff --git a/merge-ort-wrappers.h b/merge-ort-wrappers.h\nindex 0c4c57adbb..90af1f69c5 100644\n--- a/merge-ort-wrappers.h\n+++ b/merge-ort-wrappers.h\n@@ -19,7 +19,7 @@ int merge_ort_nonrecursive(struct merge_options *opt,\n int merge_ort_recursive(struct merge_options *opt,\n \t\t\tstruct commit *h1,\n \t\t\tstruct commit *h2,\n-\t\t\tstruct commit_list *ancestors,\n+\t\t\tconst struct commit_list *ancestors,\n \t\t\tstruct commit **result);\n \n #endif\ndiff --git a/merge-ort.c b/merge-ort.c\nindex eaede6cead..8ed8a4c9dc 100644\n--- a/merge-ort.c\n+++ b/merge-ort.c\n@@ -5071,11 +5071,12 @@ static void merge_ort_nonrecursive_internal(struct merge_options *opt,\n  * Originally from merge_recursive_internal(); somewhat adapted, though.\n  */\n static void merge_ort_internal(struct merge_options *opt,\n-\t\t\t       struct commit_list *merge_bases,\n+\t\t\t       const struct commit_list *_merge_bases,\n \t\t\t       struct commit *h1,\n \t\t\t       struct commit *h2,\n \t\t\t       struct merge_result *result)\n {\n+\tstruct commit_list *merge_bases = copy_commit_list(_merge_bases);\n \tstruct commit *next;\n \tstruct commit *merged_merge_bases;\n \tconst char *ancestor_name;\n@@ -5085,7 +5086,7 @@ static void merge_ort_internal(struct merge_options *opt,\n \t\tif (repo_get_merge_bases(the_repository, h1, h2,\n \t\t\t\t\t &merge_bases) < 0) {\n \t\t\tresult->clean = -1;\n-\t\t\treturn;\n+\t\t\tgoto out;\n \t\t}\n \t\t/* See merge-ort.h:merge_incore_recursive() declaration NOTE */\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n@@ -5129,7 +5130,7 @@ static void merge_ort_internal(struct merge_options *opt,\n \t\topt->branch2 = \"Temporary merge branch 2\";\n \t\tmerge_ort_internal(opt, NULL, prev, next, result);\n \t\tif (result->clean < 0)\n-\t\t\treturn;\n+\t\t\tgoto out;\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->priv->call_depth--;\n@@ -5152,6 +5153,9 @@ static void merge_ort_internal(struct merge_options *opt,\n \t\t\t\t\tresult);\n \tstrbuf_release(&merge_base_abbrev);\n \topt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */\n+\n+out:\n+\tfree_commit_list(merge_bases);\n }\n \n void merge_incore_nonrecursive(struct merge_options *opt,\n@@ -5181,7 +5185,7 @@ void merge_incore_nonrecursive(struct merge_options *opt,\n }\n \n void merge_incore_recursive(struct merge_options *opt,\n-\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    const struct commit_list *merge_bases,\n \t\t\t    struct commit *side1,\n \t\t\t    struct commit *side2,\n \t\t\t    struct merge_result *result)\ndiff --git a/merge-ort.h b/merge-ort.h\nindex ce56ec1a78..6af97c0828 100644\n--- a/merge-ort.h\n+++ b/merge-ort.h\n@@ -59,7 +59,7 @@ struct merge_result {\n  *           first\", 2006-08-09)\n  */\n void merge_incore_recursive(struct merge_options *opt,\n-\t\t\t    struct commit_list *merge_bases,\n+\t\t\t    const struct commit_list *merge_bases,\n \t\t\t    struct commit *side1,\n \t\t\t    struct commit *side2,\n \t\t\t    struct merge_result *result);\ndiff --git a/merge-recursive.c b/merge-recursive.c\nindex 832c8ef3f3..1ac0316cce 100644\n--- a/merge-recursive.c\n+++ b/merge-recursive.c\n@@ -3633,15 +3633,16 @@ static int merge_trees_internal(struct merge_options *opt,\n static int merge_recursive_internal(struct merge_options *opt,\n \t\t\t\t    struct commit *h1,\n \t\t\t\t    struct commit *h2,\n-\t\t\t\t    struct commit_list *merge_bases,\n+\t\t\t\t    const struct commit_list *_merge_bases,\n \t\t\t\t    struct commit **result)\n {\n+\tstruct commit_list *merge_bases = copy_commit_list(_merge_bases);\n \tstruct commit_list *iter;\n \tstruct commit *merged_merge_bases;\n \tstruct tree *result_tree;\n-\tint clean;\n \tconst char *ancestor_name;\n \tstruct strbuf merge_base_abbrev = STRBUF_INIT;\n+\tint ret;\n \n \tif (show(opt, 4)) {\n \t\toutput(opt, 4, _(\"Merging:\"));\n@@ -3651,8 +3652,10 @@ static int merge_recursive_internal(struct merge_options *opt,\n \n \tif (!merge_bases) {\n \t\tif (repo_get_merge_bases(the_repository, h1, h2,\n-\t\t\t\t\t &merge_bases) < 0)\n-\t\t\treturn -1;\n+\t\t\t\t\t &merge_bases) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t\tmerge_bases = reverse_commit_list(merge_bases);\n \t}\n \n@@ -3702,14 +3705,18 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\topt->branch1 = \"Temporary merge branch 1\";\n \t\topt->branch2 = \"Temporary merge branch 2\";\n \t\tif (merge_recursive_internal(opt, merged_merge_bases, iter->item,\n-\t\t\t\t\t     NULL, &merged_merge_bases) < 0)\n-\t\t\treturn -1;\n+\t\t\t\t\t     NULL, &merged_merge_bases) < 0) {\n+\t\t\tret = -1;\n+\t\t\tgoto out;\n+\t\t}\n \t\topt->branch1 = saved_b1;\n \t\topt->branch2 = saved_b2;\n \t\topt->priv->call_depth--;\n \n-\t\tif (!merged_merge_bases)\n-\t\t\treturn err(opt, _(\"merge returned no commit\"));\n+\t\tif (!merged_merge_bases) {\n+\t\t\tret = err(opt, _(\"merge returned no commit\"));\n+\t\t\tgoto out;\n+\t\t}\n \t}\n \n \t/*\n@@ -3726,17 +3733,16 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\trepo_read_index(opt->repo);\n \n \topt->ancestor = ancestor_name;\n-\tclean = merge_trees_internal(opt,\n-\t\t\t\t     repo_get_commit_tree(opt->repo, h1),\n-\t\t\t\t     repo_get_commit_tree(opt->repo, h2),\n-\t\t\t\t     repo_get_commit_tree(opt->repo,\n-\t\t\t\t\t\t\t  merged_merge_bases),\n-\t\t\t\t     &result_tree);\n-\tstrbuf_release(&merge_base_abbrev);\n+\tret = merge_trees_internal(opt,\n+\t\t\t\t   repo_get_commit_tree(opt->repo, h1),\n+\t\t\t\t   repo_get_commit_tree(opt->repo, h2),\n+\t\t\t\t   repo_get_commit_tree(opt->repo,\n+\t\t\t\t\t\t\tmerged_merge_bases),\n+\t\t\t\t   &result_tree);\n \topt->ancestor = NULL;  /* avoid accidental re-use of opt->ancestor */\n-\tif (clean < 0) {\n+\tif (ret < 0) {\n \t\tflush_output(opt);\n-\t\treturn clean;\n+\t\tgoto out;\n \t}\n \n \tif (opt->priv->call_depth) {\n@@ -3745,7 +3751,11 @@ static int merge_recursive_internal(struct merge_options *opt,\n \t\tcommit_list_insert(h1, &(*result)->parents);\n \t\tcommit_list_insert(h2, &(*result)->parents->next);\n \t}\n-\treturn clean;\n+\n+out:\n+\tstrbuf_release(&merge_base_abbrev);\n+\tfree_commit_list(merge_bases);\n+\treturn ret;\n }\n \n static int merge_start(struct merge_options *opt, struct tree *head)\n@@ -3827,7 +3837,7 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *merge_bases,\n+\t\t    const struct commit_list *merge_bases,\n \t\t    struct commit **result)\n {\n \tint clean;\n@@ -3895,6 +3905,7 @@ int merge_recursive_generic(struct merge_options *opt,\n \trepo_hold_locked_index(opt->repo, &lock, LOCK_DIE_ON_ERROR);\n \tclean = merge_recursive(opt, head_commit, next_commit, ca,\n \t\t\t\tresult);\n+\tfree_commit_list(ca);\n \tif (clean < 0) {\n \t\trollback_lock_file(&lock);\n \t\treturn clean;\ndiff --git a/merge-recursive.h b/merge-recursive.h\nindex 839eb6436e..3136c7cc2d 100644\n--- a/merge-recursive.h\n+++ b/merge-recursive.h\n@@ -104,7 +104,7 @@ int merge_trees(struct merge_options *opt,\n int merge_recursive(struct merge_options *opt,\n \t\t    struct commit *h1,\n \t\t    struct commit *h2,\n-\t\t    struct commit_list *merge_bases,\n+\t\t    const struct commit_list *merge_bases,\n \t\t    struct commit **result);\n \n /*\ndiff --git a/sequencer.c b/sequencer.c\nindex 20807ea7e5..131443c242 100644\n--- a/sequencer.c\n+++ b/sequencer.c\n@@ -4315,6 +4315,7 @@ static int do_merge(struct repository *r,\n \tstrbuf_release(&ref_name);\n \trollback_lock_file(&lock);\n \tfree_commit_list(to_merge);\n+\tfree_commit_list(bases);\n \treturn ret;\n }\n \ndiff --git a/t/t3430-rebase-merges.sh b/t/t3430-rebase-merges.sh\nindex 59b5d6b6f2..36ca126bcd 100755\n--- a/t/t3430-rebase-merges.sh\n+++ b/t/t3430-rebase-merges.sh\n@@ -21,6 +21,7 @@ Initial setup:\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 . \"$TEST_DIRECTORY\"/lib-rebase.sh\n . \"$TEST_DIRECTORY\"/lib-log-graph.sh\ndiff --git a/t/t6402-merge-rename.sh b/t/t6402-merge-rename.sh\nindex 2738b50c2a..729aac9842 100755\n--- a/t/t6402-merge-rename.sh\n+++ b/t/t6402-merge-rename.sh\n@@ -4,6 +4,7 @@ test_description='Merge-recursive merging renames'\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 modify () {\ndiff --git a/t/t6430-merge-recursive.sh b/t/t6430-merge-recursive.sh\nindex ca15e6dd6d..555f00f78a 100755\n--- a/t/t6430-merge-recursive.sh\n+++ b/t/t6430-merge-recursive.sh\n@@ -5,6 +5,7 @@ test_description='merge-recursive backend test'\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 . \"$TEST_DIRECTORY\"/lib-merge.sh\n \ndiff --git a/t/t6436-merge-overwrite.sh b/t/t6436-merge-overwrite.sh\nindex 4f4376421e..ccc620477d 100755\n--- a/t/t6436-merge-overwrite.sh\n+++ b/t/t6436-merge-overwrite.sh\n@@ -7,6 +7,7 @@ Do not overwrite changes.'\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 test_expect_success 'setup' '\ndiff --git a/t/t7611-merge-abort.sh b/t/t7611-merge-abort.sh\nindex d6975ca48d..992a8f9874 100755\n--- a/t/t7611-merge-abort.sh\n+++ b/t/t7611-merge-abort.sh\n@@ -25,6 +25,7 @@ Next, test git merge --abort with the following variables:\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 test_expect_success 'setup' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496886","messageId":"67f8637dc66530f05678f327d18370b524448481.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 26/29] line-range: plug leaking find functions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:21:15Z","receivedAt":"2024-06-11T09:21:19Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `parse_range_funcname()` we may end up allocating a \"find function\",\nbut never free it. Fix this.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n line-range.c | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/line-range.c b/line-range.c\nindex 60f0e5ada8..b99f0d9895 100644\n--- a/line-range.c\n+++ b/line-range.c\n@@ -234,6 +234,8 @@ static const char *parse_range_funcname(\n \t}\n \n \tregfree(&regexp);\n+\tif (xecfg)\n+\t\txdiff_clear_find_func(xecfg);\n \tfree(xecfg);\n \tfree(pattern);\n \n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496887","messageId":"bd6db56a3f926abf73939e5094983e630a38db27.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 27/29] blame: fix leaking data for blame scoreboards","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:21:20Z","receivedAt":"2024-06-11T09:21:24Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"There are some memory leaks when cleaning up blame scoreboards. Fix\nthose.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n blame.c                           | 4 ++++\n t/t4061-diff-indent.sh            | 1 +\n t/t8002-blame.sh                  | 1 +\n t/t8004-blame-with-conflicts.sh   | 1 +\n t/t8006-blame-textconv.sh         | 2 ++\n t/t8009-blame-vs-topicbranches.sh | 2 ++\n t/t8011-blame-split-file.sh       | 2 ++\n t/t8012-blame-colors.sh           | 1 +\n t/t8014-blame-ignore-fuzzy.sh     | 2 ++\n 9 files changed, 16 insertions(+)\n\ndiff --git a/blame.c b/blame.c\nindex 33586b9777..f97d0e9e1a 100644\n--- a/blame.c\n+++ b/blame.c\n@@ -2928,6 +2928,10 @@ void setup_blame_bloom_data(struct blame_scoreboard *sb)\n \n void cleanup_scoreboard(struct blame_scoreboard *sb)\n {\n+\tfree(sb->lineno);\n+\tclear_prio_queue(&sb->commits);\n+\toidset_clear(&sb->ignore_list);\n+\n \tif (sb->bloom_data) {\n \t\tint i;\n \t\tfor (i = 0; i < sb->bloom_data->nr; i++) {\ndiff --git a/t/t4061-diff-indent.sh b/t/t4061-diff-indent.sh\nindex 7750b87ca1..2942e5d9b9 100755\n--- a/t/t4061-diff-indent.sh\n+++ b/t/t4061-diff-indent.sh\n@@ -6,6 +6,7 @@ test_description='Test diff indent heuristic.\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 . \"$TEST_DIRECTORY\"/lib-diff.sh\n \ndiff --git a/t/t8002-blame.sh b/t/t8002-blame.sh\nindex 0147de304b..3596634039 100755\n--- a/t/t8002-blame.sh\n+++ b/t/t8002-blame.sh\n@@ -5,6 +5,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n PROG='git blame -c'\ndiff --git a/t/t8004-blame-with-conflicts.sh b/t/t8004-blame-with-conflicts.sh\nindex 35414a5336..2c2a0b33f9 100755\n--- a/t/t8004-blame-with-conflicts.sh\n+++ b/t/t8004-blame-with-conflicts.sh\n@@ -6,6 +6,7 @@ test_description='git blame on conflicted files'\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 test_expect_success 'setup first case' '\ndiff --git a/t/t8006-blame-textconv.sh b/t/t8006-blame-textconv.sh\nindex 7683515155..42f8be25a3 100755\n--- a/t/t8006-blame-textconv.sh\n+++ b/t/t8006-blame-textconv.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='git blame textconv support'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n find_blame() {\ndiff --git a/t/t8009-blame-vs-topicbranches.sh b/t/t8009-blame-vs-topicbranches.sh\nindex 72596e38b2..30331713b9 100755\n--- a/t/t8009-blame-vs-topicbranches.sh\n+++ b/t/t8009-blame-vs-topicbranches.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='blaming trough history with topic branches'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Creates the history shown below. '*'s mark the first parent in the merges.\ndiff --git a/t/t8011-blame-split-file.sh b/t/t8011-blame-split-file.sh\nindex bdda0c03fe..da1801f4d2 100755\n--- a/t/t8011-blame-split-file.sh\n+++ b/t/t8011-blame-split-file.sh\n@@ -10,6 +10,8 @@ Note that we need to use \"blame -C\" to find the commit for all lines. We will\n not bother testing that the non-C case fails to find it. That is how blame\n behaves now, but it is not a property we want to make sure is retained.\n '\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # help avoid typing and reading long strings of similar lines\ndiff --git a/t/t8012-blame-colors.sh b/t/t8012-blame-colors.sh\nindex c3a5f6d01f..9a79c109f2 100755\n--- a/t/t8012-blame-colors.sh\n+++ b/t/t8012-blame-colors.sh\n@@ -5,6 +5,7 @@ GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME=main\n export GIT_TEST_DEFAULT_INITIAL_BRANCH_NAME\n \n TEST_CREATE_REPO_NO_TEMPLATE=1\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n PROG='git blame -c'\ndiff --git a/t/t8014-blame-ignore-fuzzy.sh b/t/t8014-blame-ignore-fuzzy.sh\nindex 0bd0341301..933222cea1 100755\n--- a/t/t8014-blame-ignore-fuzzy.sh\n+++ b/t/t8014-blame-ignore-fuzzy.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='git blame ignore fuzzy heuristic'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n pick_author='s/^[0-9a-f^]* *(\\([^ ]*\\) .*/\\1/'\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496888","messageId":"c1291025fadceed5a92d9c8f1d611e26d9b15d2b.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 28/29] builtin/blame: fix leaking prefixed paths","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:21:25Z","receivedAt":"2024-06-11T09:21:29Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"In `cmd_blame()` we compute prefixed paths by calling `add_prefix()`,\nwhich itself calls `prefix_path()`. While `prefix_path()` returns an\nallocated string, `add_prefix()` pretends to return a constant string.\nConsequently, this path never gets freed.\n\nFix the return type to be `char *` and free the path to plug the memory\nleak.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/blame.c               | 5 +++--\n t/t6130-pathspec-noglob.sh    | 2 ++\n t/t7010-setup.sh              | 1 +\n t/t8003-blame-corner-cases.sh | 1 +\n t/t8008-blame-formats.sh      | 2 ++\n 5 files changed, 9 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex e09ff0155a..17694410ed 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -687,7 +687,7 @@ static unsigned parse_score(const char *arg)\n \treturn score;\n }\n \n-static const char *add_prefix(const char *prefix, const char *path)\n+static char *add_prefix(const char *prefix, const char *path)\n {\n \treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n@@ -865,7 +865,7 @@ static void build_ignorelist(struct blame_scoreboard *sb,\n int cmd_blame(int argc, const char **argv, const char *prefix)\n {\n \tstruct rev_info revs;\n-\tconst char *path;\n+\tchar *path = NULL;\n \tstruct blame_scoreboard sb;\n \tstruct blame_origin *o;\n \tstruct blame_entry *ent = NULL;\n@@ -1226,6 +1226,7 @@ int cmd_blame(int argc, const char **argv, const char *prefix)\n \t}\n \n cleanup:\n+\tfree(path);\n \tcleanup_scoreboard(&sb);\n \trelease_revisions(&revs);\n \treturn 0;\ndiff --git a/t/t6130-pathspec-noglob.sh b/t/t6130-pathspec-noglob.sh\nindex ba7902c9cd..82de25d549 100755\n--- a/t/t6130-pathspec-noglob.sh\n+++ b/t/t6130-pathspec-noglob.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='test globbing (and noglob) of pathspec limiting'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'create commits with glob characters' '\ndiff --git a/t/t7010-setup.sh b/t/t7010-setup.sh\nindex 520f96d09f..d9add2162e 100755\n--- a/t/t7010-setup.sh\n+++ b/t/t7010-setup.sh\n@@ -2,6 +2,7 @@\n \n test_description='setup taking and sanitizing funny paths'\n \n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success setup '\ndiff --git a/t/t8003-blame-corner-cases.sh b/t/t8003-blame-corner-cases.sh\nindex 731265541a..6288352f57 100755\n--- a/t/t8003-blame-corner-cases.sh\n+++ b/t/t8003-blame-corner-cases.sh\n@@ -4,6 +4,7 @@ test_description='git blame corner cases'\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 pick_fc='s/^[0-9a-f^]* *\\([^ ]*\\) *(\\([^ ]*\\) .*/\\1-\\2/'\ndiff --git a/t/t8008-blame-formats.sh b/t/t8008-blame-formats.sh\nindex ae4b579d24..fb5d225a67 100755\n--- a/t/t8008-blame-formats.sh\n+++ b/t/t8008-blame-formats.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='blame output in various formats on a simple case'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n test_expect_success 'setup' '\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"496889","messageId":"5d7007e724b0ffaadacbe1e661d0a0ff00faa36e.1718095906.git.ps@pks.im","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"[PATCH v2 29/29] builtin/blame: fix leaking ignore revs files","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2024-06-11T09:21:29Z","receivedAt":"2024-06-11T09:21:33Z","isPatch":true,"sender":{"key":"ps@pks.im","avatar":"https://avatars.githubusercontent.com/u/4056630?v=4"},"body":"When parsing the blame configuration we add \"blame.ignoreRevsFile\"\nconfigs to a string list. This string list is declared as with `NODUP`,\nand thus we hand over the allocated string to that list. We eventually\nend up calling `string_list_clear()` on that list, but due to it being\ndeclared as `NODUP` we will not release the associated strings and thus\nleak memory.\n\nFix this issue by setting up the list as `DUP` instead and free the\nconfig string after insertion.\n\nSigned-off-by: Patrick Steinhardt <ps@pks.im>\n---\n builtin/blame.c              | 3 ++-\n t/t8013-blame-ignore-revs.sh | 2 ++\n 2 files changed, 4 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/blame.c b/builtin/blame.c\nindex 17694410ed..702fe4fb94 100644\n--- a/builtin/blame.c\n+++ b/builtin/blame.c\n@@ -67,7 +67,7 @@ static int no_whole_file_rename;\n static int show_progress;\n static char repeated_meta_color[COLOR_MAXLEN];\n static int coloring_mode;\n-static struct string_list ignore_revs_file_list = STRING_LIST_INIT_NODUP;\n+static struct string_list ignore_revs_file_list = STRING_LIST_INIT_DUP;\n static int mark_unblamable_lines;\n static int mark_ignored_lines;\n \n@@ -725,6 +725,7 @@ static int git_blame_config(const char *var, const char *value,\n \t\tif (ret)\n \t\t\treturn ret;\n \t\tstring_list_insert(&ignore_revs_file_list, str);\n+\t\tfree(str);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"blame.markunblamablelines\")) {\ndiff --git a/t/t8013-blame-ignore-revs.sh b/t/t8013-blame-ignore-revs.sh\nindex dbfbd86e83..d33788d867 100755\n--- a/t/t8013-blame-ignore-revs.sh\n+++ b/t/t8013-blame-ignore-revs.sh\n@@ -1,6 +1,8 @@\n #!/bin/sh\n \n test_description='ignore revisions when blaming'\n+\n+TEST_PASSES_SANITIZE_LEAK=true\n . ./test-lib.sh\n \n # Creates:\n-- \n2.45.2.436.gcd77e87115.dirty\n\n"},{"id":"497007","messageId":"7b62e733-0816-43be-a1b2-4d7d288ec6ee@gmail.com","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"Re: [PATCH v2 00/29] Memory leak fixes (pt.2)","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2024-06-12T09:09:48Z","receivedAt":"2024-06-12T09:09:52Z","isPatch":true,"sender":{"key":"phillip.wood@dunelm.org.uk","avatar":null},"body":"Hi Patrick\n\nOn 11/06/2024 10:19, Patrick Steinhardt wrote:\n> Hi,\n> \n> this is the second version of my patch series that fixes various memory\n> leaks in our codebase.\n> \n> 20:  48bcd0ac80 ! 20:  144eb23617 sequencer: fix leaking string buffer in `commit_staged_changes()`\n>      @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>        \t\t\t/*\n>        \t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n>       @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>      + \t\t\t\t * We need to update the squash message to skip\n>      + \t\t\t\t * the latest commit message.\n>      + \t\t\t\t */\n>      +-\t\t\t\tint res = 0;\n>      + \t\t\t\tstruct commit *commit;\n>      + \t\t\t\tconst char *msg;\n>        \t\t\t\tconst char *path = rebase_path_squash_msg();\n>        \t\t\t\tconst char *encoding = get_commit_output_encoding();\n>        \n>      @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>        \n>        \t\t\t\tp = repo_logmsg_reencode(r, commit, NULL, encoding);\n>        \t\t\t\tif (!p)  {\n>      -@@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>      +-\t\t\t\t\tres = error(_(\"could not parse commit %s\"),\n>      ++\t\t\t\t\tret = error(_(\"could not parse commit %s\"),\n>      + \t\t\t\t\t\t    oid_to_hex(&commit->object.oid));\n>      + \t\t\t\t\tgoto unuse_commit_buffer;\n>      + \t\t\t\t}\n>      + \t\t\t\tfind_commit_subject(p, &msg);\n>      + \t\t\t\tif (write_message(msg, strlen(msg), path, 0)) {\n>      +-\t\t\t\t\tres = error(_(\"could not write file: \"\n>      ++\t\t\t\t\tret = error(_(\"could not write file: \"\n>      + \t\t\t\t\t\t       \"'%s'\"), path);\n>      + \t\t\t\t\tgoto unuse_commit_buffer;\n>        \t\t\t\t}\n>      ++\n>      ++\t\t\t\tret = 0;\n>      ++\n>        \t\t\tunuse_commit_buffer:\n>        \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n>       -\t\t\t\tif (res)\n>       -\t\t\t\t\treturn res;\n>      -+\t\t\t\tif (res) {\n>      -+\t\t\t\t\tret = res;\n>      ++\t\t\t\tif (ret)\n>       +\t\t\t\t\tgoto out;\n>      -+\t\t\t\t}\n>        \t\t\t}\n>        \t\t}\n\nThis looks good, thanks for tweaking it\n\nBest Wishes\n\nPhillip\n"},{"id":"497011","messageId":"CAOLa=ZQDCwE2KZVBV16vtA9-=iqLW7c1TAFv4Cj0SQPwEC=8FQ@mail.gmail.com","threadId":"61578","inReplyTo":"4ba1343d-1467-46a0-85b0-b249e1ddedf7@gmail.com","subject":"Re: [PATCH 13/29] merge-recursive: fix memory leak when finalizing merge","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-06-12T09:33:43Z","receivedAt":"2024-06-12T09:33:45Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> On 06/06/2024 11:50, Karthik Nayak wrote:\n>> Patrick Steinhardt <ps@pks.im> writes:\n>>\n>>> We do not free some members of `struct merge_options`' private data.\n>>\n>> Nit: s/`struct merge_options`'/`struct merge_options`'s/\n>\n> Either is correct, it is a matter of style whether one adds the trailing\n> \"s\".\n>\n> Best Wishes\n>\n> Phillip\n\nTIL, thanks Phillip.\n"},{"id":"497012","messageId":"CAOLa=ZRBrR5X1bQFAEqM3Ovx5G=J9aqdyD2XKrGT9s6j1jEnnQ@mail.gmail.com","threadId":"61578","inReplyTo":"cover.1718095906.git.ps@pks.im","subject":"Re: [PATCH v2 00/29] Memory leak fixes (pt.2)","fromName":"Karthik Nayak","fromEmail":"karthik.188@gmail.com","sentAt":"2024-06-12T09:35:16Z","receivedAt":"2024-06-12T09:35:17Z","isPatch":true,"sender":{"key":"karthik.188@gmail.com","avatar":"https://avatars.githubusercontent.com/u/1786334?v=4"},"body":"Patrick Steinhardt <ps@pks.im> writes:\n\n> Hi,\n>\n> this is the second version of my patch series that fixes various memory\n> leaks in our codebase.\n>\n> Changes compared to v1:\n>\n>   - Fix various typos in commit messages.\n>\n>   - Refactor patch 20 such that we do not have both `ret` and `res` as\n>     local variables, which was confusing.\n>\n> Thanks!\n>\n\nThe changes with the range-diff looks good to me, Thanks!\n\n> Patrick\n>\n> Patrick Steinhardt (29):\n>   revision: fix memory leak when reversing revisions\n>   parse-options: fix leaks for users of OPT_FILENAME\n>   notes-utils: free note trees when releasing copied notes\n>   bundle: plug leaks in `create_bundle()`\n>   biultin/rev-parse: fix memory leaks in `--parseopt` mode\n>   merge-recursive: fix leaking rename conflict info\n>   revision: fix leaking display notes\n>   notes: fix memory leak when pruning notes\n>   builtin/rev-list: fix leaking bitmap index when calculating disk usage\n>   object-name: free leaking object contexts\n>   builtin/difftool: plug memory leaks in `run_dir_diff()`\n>   builtin/merge-recursive: fix leaking object ID bases\n>   merge-recursive: fix memory leak when finalizing merge\n>   builtin/log: fix leaking commit list in git-cherry(1)\n>   revision: free diff options\n>   builtin/stash: fix leak in `show_stash()`\n>   rerere: fix various trivial leaks\n>   config: fix leaking \"core.notesref\" variable\n>   commit: fix leaking parents when calling `commit_tree_extended()`\n>   sequencer: fix leaking string buffer in `commit_staged_changes()`\n>   apply: fix leaking string in `match_fragment()`\n>   builtin/clone: plug leaking HEAD ref in `wanted_peer_refs()`\n>   sequencer: fix memory leaks in `make_script_with_merges()`\n>   builtin/merge: fix leaking `struct cmdnames` in `get_strategy()`\n>   merge: fix leaking merge bases\n>   line-range: plug leaking find functions\n>   blame: fix leaking data for blame scoreboards\n>   builtin/blame: fix leaking prefixed paths\n>   builtin/blame: fix leaking ignore revs files\n>\n>  apply.c                                   |  88 ++++++++++------\n>  apply.h                                   |   2 +-\n>  blame.c                                   |   4 +\n>  builtin/am.c                              |   7 +-\n>  builtin/archive.c                         |   7 +-\n>  builtin/blame.c                           |   8 +-\n>  builtin/cat-file.c                        |  17 ++--\n>  builtin/clone.c                           |   3 +-\n>  builtin/commit-tree.c                     |  11 +-\n>  builtin/commit.c                          |  10 +-\n>  builtin/difftool.c                        |   3 +\n>  builtin/fmt-merge-msg.c                   |   4 +-\n>  builtin/grep.c                            |   4 +-\n>  builtin/log.c                             |  16 +--\n>  builtin/ls-tree.c                         |   3 +-\n>  builtin/merge-recursive.c                 |   6 +-\n>  builtin/merge-tree.c                      |   1 +\n>  builtin/merge.c                           |  18 +++-\n>  builtin/multi-pack-index.c                |  13 ++-\n>  builtin/replay.c                          |  14 ++-\n>  builtin/rev-list.c                        |   2 +\n>  builtin/rev-parse.c                       |  55 +++++-----\n>  builtin/shortlog.c                        |   5 +-\n>  builtin/sparse-checkout.c                 |   1 +\n>  builtin/stash.c                           |  23 +++--\n>  bundle.c                                  |  29 ++++--\n>  commit.c                                  |  28 +++---\n>  commit.h                                  |  12 +--\n>  config.c                                  |   1 +\n>  diff-lib.c                                |   2 +\n>  diff.c                                    |   8 +-\n>  help.c                                    |  12 +--\n>  help.h                                    |   2 +\n>  line-range.c                              |   2 +\n>  list-objects-filter.c                     |   2 +\n>  log-tree.c                                |   1 +\n>  merge-ort-wrappers.c                      |   2 +-\n>  merge-ort-wrappers.h                      |   2 +-\n>  merge-ort.c                               |  12 ++-\n>  merge-ort.h                               |   2 +-\n>  merge-recursive.c                         |  68 ++++++++-----\n>  merge-recursive.h                         |   4 +-\n>  notes-merge.c                             |   1 +\n>  notes-utils.c                             |   9 +-\n>  notes-utils.h                             |   2 +-\n>  notes.c                                   |  21 ++--\n>  notes.h                                   |   5 +\n>  object-name.c                             |  40 ++++++--\n>  object-name.h                             |   2 +\n>  rerere.c                                  |   3 +\n>  revision.c                                |  59 +++++++----\n>  sequencer.c                               | 116 +++++++++++++++-------\n>  t/helper/test-parse-options.c             |   1 +\n>  t/t1004-read-tree-m-u-wf.sh               |   1 +\n>  t/t1015-read-index-unmerged.sh            |   2 +\n>  t/t1021-rerere-in-workdir.sh              |   1 +\n>  t/t1512-rev-parse-disambiguation.sh       |   1 +\n>  t/t2500-untracked-overwriting.sh          |   1 +\n>  t/t3301-notes.sh                          |   1 +\n>  t/t3306-notes-prune.sh                    |   1 +\n>  t/t3308-notes-merge.sh                    |   1 +\n>  t/t3309-notes-merge-auto-resolve.sh       |   1 +\n>  t/t3400-rebase.sh                         |   1 +\n>  t/t3401-rebase-and-am-rename.sh           |   1 +\n>  t/t3403-rebase-skip.sh                    |   1 +\n>  t/t3406-rebase-message.sh                 |   1 +\n>  t/t3407-rebase-abort.sh                   |   1 +\n>  t/t3417-rebase-whitespace-fix.sh          |   1 +\n>  t/t3418-rebase-continue.sh                |   1 +\n>  t/t3420-rebase-autostash.sh               |   1 +\n>  t/t3421-rebase-topology-linear.sh         |   2 +\n>  t/t3424-rebase-empty.sh                   |   1 +\n>  t/t3428-rebase-signoff.sh                 |   1 +\n>  t/t3430-rebase-merges.sh                  |   1 +\n>  t/t3434-rebase-i18n.sh                    |   1 +\n>  t/t3500-cherry.sh                         |   1 +\n>  t/t3504-cherry-pick-rerere.sh             |   1 +\n>  t/t3505-cherry-pick-empty.sh              |   1 +\n>  t/t3508-cherry-pick-many-commits.sh       |   1 +\n>  t/t3509-cherry-pick-merge-df.sh           |   1 +\n>  t/t3907-stash-show-config.sh              |   1 +\n>  t/t4061-diff-indent.sh                    |   1 +\n>  t/t4131-apply-fake-ancestor.sh            |   1 +\n>  t/t4151-am-abort.sh                       |   1 +\n>  t/t4153-am-resume-override-opts.sh        |   1 +\n>  t/t4208-log-magic-pathspec.sh             |   1 +\n>  t/t4253-am-keep-cr-dos.sh                 |   1 +\n>  t/t4255-am-submodule.sh                   |   1 +\n>  t/t5150-request-pull.sh                   |   1 +\n>  t/t5300-pack-object.sh                    |   4 +-\n>  t/t5305-include-tag.sh                    |   1 +\n>  t/t5407-post-rewrite-hook.sh              |   1 +\n>  t/t5605-clone-local.sh                    |   1 +\n>  t/t5607-clone-bundle.sh                   |   1 +\n>  t/t5612-clone-refspec.sh                  |   1 +\n>  t/t6000-rev-list-misc.sh                  |   1 +\n>  t/t6001-rev-list-graft.sh                 |   1 +\n>  t/t6013-rev-list-reverse-parents.sh       |   1 +\n>  t/t6017-rev-list-stdin.sh                 |   1 +\n>  t/t6020-bundle-misc.sh                    |   1 +\n>  t/t6115-rev-list-du.sh                    |   2 +\n>  t/t6130-pathspec-noglob.sh                |   2 +\n>  t/t6402-merge-rename.sh                   |   1 +\n>  t/t6427-diff3-conflict-markers.sh         |   1 +\n>  t/t6430-merge-recursive.sh                |   1 +\n>  t/t6432-merge-recursive-space-options.sh  |   1 +\n>  t/t6434-merge-recursive-rename-options.sh |   1 +\n>  t/t6436-merge-overwrite.sh                |   1 +\n>  t/t7006-pager.sh                          |   1 +\n>  t/t7010-setup.sh                          |   1 +\n>  t/t7012-skip-worktree-writing.sh          |   1 +\n>  t/t7201-co.sh                             |   1 +\n>  t/t7501-commit-basic-functionality.sh     |   1 +\n>  t/t7505-prepare-commit-msg-hook.sh        |   1 +\n>  t/t7512-status-help.sh                    |   1 +\n>  t/t7600-merge.sh                          |   1 +\n>  t/t7606-merge-custom.sh                   |   1 +\n>  t/t7611-merge-abort.sh                    |   1 +\n>  t/t8002-blame.sh                          |   1 +\n>  t/t8003-blame-corner-cases.sh             |   1 +\n>  t/t8004-blame-with-conflicts.sh           |   1 +\n>  t/t8006-blame-textconv.sh                 |   2 +\n>  t/t8008-blame-formats.sh                  |   2 +\n>  t/t8009-blame-vs-topicbranches.sh         |   2 +\n>  t/t8011-blame-split-file.sh               |   2 +\n>  t/t8012-blame-colors.sh                   |   1 +\n>  t/t8013-blame-ignore-revs.sh              |   2 +\n>  t/t8014-blame-ignore-fuzzy.sh             |   2 +\n>  t/t9500-gitweb-standalone-no-errors.sh    |   1 +\n>  t/t9502-gitweb-standalone-parse-output.sh |   1 +\n>  130 files changed, 591 insertions(+), 272 deletions(-)\n>\n> Range-diff against v1:\n>  1:  a88a6fa266 =  1:  f602bb5444 revision: fix memory leak when reversing revisions\n>  2:  ecabbb74e1 =  2:  84f2f56b71 parse-options: fix leaks for users of OPT_FILENAME\n>  3:  17a8f53275 !  3:  c5f49bf930 notes-utils: free note trees when releasing copied notes\n>     @@ Metadata\n>       ## Commit message ##\n>          notes-utils: free note trees when releasing copied notes\n>\n>     -    While we clear most of the members of `struct notet_rewrite_cfg` in\n>     +    While we clear most of the members of `struct notes_rewrite_cfg` in\n>          `finish_copy_notes_for_rewrite()`, we do not clear the notes tree. Fix\n>          this to plug this memory leak.\n>\n>  4:  0c8c97cded =  4:  5a1eae2cc9 bundle: plug leaks in `create_bundle()`\n>  5:  620814fb99 =  5:  91eefcf64a biultin/rev-parse: fix memory leaks in `--parseopt` mode\n>  6:  0052d60ee8 !  6:  f16c29a20a merge-recursive: fix leaging rename conflict info\n>     @@ Metadata\n>      Author: Patrick Steinhardt <ps@pks.im>\n>\n>       ## Commit message ##\n>     -    merge-recursive: fix leaging rename conflict info\n>     +    merge-recursive: fix leaking rename conflict info\n>\n>          When computing rename conflicts in our recursive merge algorithm we set\n>          up `struct rename_conflict_info`s to track that information. We never\n>          free those data structures though and thus leak memory.\n>\n>          We need to be a bit more careful here though because the same rename\n>     -    conflict info can be assigned to multiple structures. Accomodate for\n>     +    conflict info can be assigned to multiple structures. Accommodate for\n>          this by introducing a `rename_conflict_info_owned` bit that we can use\n>          to steer whether or not the rename conflict info shall be free'd.\n>\n>  7:  8c55370b8e =  7:  b9c3a34ae1 revision: fix leaking display notes\n>  8:  17671dc9cf =  8:  3ea5e5c6bd notes: fix memory leak when pruning notes\n>  9:  5e01c39e35 =  9:  a0242359b7 builtin/rev-list: fix leaking bitmap index when calculating disk usage\n> 10:  2ac24bf942 = 10:  bdb7389328 object-name: free leaking object contexts\n> 11:  8349f6acbd = 11:  e6ff9a3f1c builtin/difftool: plug memory leaks in `run_dir_diff()`\n> 12:  5d9fb78e63 = 12:  fb30a254ab builtin/merge-recursive: fix leaking object ID bases\n> 13:  930de11911 = 13:  b6540e4aff merge-recursive: fix memory leak when finalizing merge\n> 14:  3e3243f884 = 14:  7cb4f75178 builtin/log: fix leaking commit list in git-cherry(1)\n> 15:  a754800fd1 = 15:  99f4f3d341 revision: free diff options\n> 16:  8bde010d1d = 16:  707d798f78 builtin/stash: fix leak in `show_stash()`\n> 17:  cadeb09b95 = 17:  7d73c50872 rerere: fix various trivial leaks\n> 18:  cff6303ce1 = 18:  1700ec8d21 config: fix leaking \"core.notesref\" variable\n> 19:  8ada8864d8 = 19:  c1a1eb34ab commit: fix leaking parents when calling `commit_tree_extended()`\n> 20:  48bcd0ac80 ! 20:  144eb23617 sequencer: fix leaking string buffer in `commit_staged_changes()`\n>     @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>       \t\t\t/*\n>       \t\t\t * If a fixup/squash in a fixup/squash chain failed, the\n>      @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>     + \t\t\t\t * We need to update the squash message to skip\n>     + \t\t\t\t * the latest commit message.\n>     + \t\t\t\t */\n>     +-\t\t\t\tint res = 0;\n>     + \t\t\t\tstruct commit *commit;\n>     + \t\t\t\tconst char *msg;\n>       \t\t\t\tconst char *path = rebase_path_squash_msg();\n>       \t\t\t\tconst char *encoding = get_commit_output_encoding();\n>\n>     @@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>\n>       \t\t\t\tp = repo_logmsg_reencode(r, commit, NULL, encoding);\n>       \t\t\t\tif (!p)  {\n>     -@@ sequencer.c: static int commit_staged_changes(struct repository *r,\n>     +-\t\t\t\t\tres = error(_(\"could not parse commit %s\"),\n>     ++\t\t\t\t\tret = error(_(\"could not parse commit %s\"),\n>     + \t\t\t\t\t\t    oid_to_hex(&commit->object.oid));\n>     + \t\t\t\t\tgoto unuse_commit_buffer;\n>     + \t\t\t\t}\n>     + \t\t\t\tfind_commit_subject(p, &msg);\n>     + \t\t\t\tif (write_message(msg, strlen(msg), path, 0)) {\n>     +-\t\t\t\t\tres = error(_(\"could not write file: \"\n>     ++\t\t\t\t\tret = error(_(\"could not write file: \"\n>     + \t\t\t\t\t\t       \"'%s'\"), path);\n>     + \t\t\t\t\tgoto unuse_commit_buffer;\n>       \t\t\t\t}\n>     ++\n>     ++\t\t\t\tret = 0;\n>     ++\n>       \t\t\tunuse_commit_buffer:\n>       \t\t\t\trepo_unuse_commit_buffer(r, commit, p);\n>      -\t\t\t\tif (res)\n>      -\t\t\t\t\treturn res;\n>     -+\t\t\t\tif (res) {\n>     -+\t\t\t\t\tret = res;\n>     ++\t\t\t\tif (ret)\n>      +\t\t\t\t\tgoto out;\n>     -+\t\t\t\t}\n>       \t\t\t}\n>       \t\t}\n>\n> 21:  ebed18717f = 21:  8e1cf8a18b apply: fix leaking string in `match_fragment()`\n> 22:  4f4ae5ea96 = 22:  8090891bd0 builtin/clone: plug leaking HEAD ref in `wanted_peer_refs()`\n> 23:  713531c6f1 = 23:  1cdd988c45 sequencer: fix memory leaks in `make_script_with_merges()`\n> 24:  eeef280dd5 = 24:  be9bccc853 builtin/merge: fix leaking `struct cmdnames` in `get_strategy()`\n> 25:  5441f1e2ef = 25:  6f6456494c merge: fix leaking merge bases\n> 26:  ba9c2b4ea7 = 26:  67f8637dc6 line-range: plug leaking find functions\n> 27:  5f2b0adc56 = 27:  bd6db56a3f blame: fix leaking data for blame scoreboards\n> 28:  7ee8734437 = 28:  c1291025fa builtin/blame: fix leaking prefixed paths\n> 29:  5b5fdfc6cb = 29:  5d7007e724 builtin/blame: fix leaking ignore revs files\n> --\n> 2.45.2.436.gcd77e87115.dirty\n"}]}