]> git.ipfire.org Git - thirdparty/git.git/commitdiff
merge: fix leak with merge.defaultToUpstream
authorToon Claes <toon@iotcl.com>
Tue, 28 Jul 2026 13:00:04 +0000 (15:00 +0200)
committerJunio C Hamano <gitster@pobox.com>
Tue, 28 Jul 2026 18:07:19 +0000 (11:07 -0700)
By default the setting 'merge.defaultToUpstream' for git-merge(1) is set
to 'true', which means when `git merge` is invoked with no arguments it
merges the upstream branch configured for the current branch.

With this configuration set to 'true', setup_with_upstream() is called.
That function allocates an array of arguments and hands it back to
cmd_merge() via its `argv` parameter. This array is never freed, so
cmd_merge() leaks it on every invocation.

Track the allocated array in a separate variable and free it at the end.

The leak has been present since 93e535a5b7 (merge: merge with the
default upstream branch without argument, 2011-03-24). Although the leak
sanitizer was enabled for tests in fc1ddf42af (t: remove
TEST_PASSES_SANITIZE_LEAK annotations, 2024-11-21), it went unnoticed
because no test calls `git merge` without arguments, exercising the
default-to-upstream path. Add such a test in t7600, which fails under
the leak sanitizer without this fix.

Signed-off-by: Toon Claes <toon@iotcl.com>
Acked-by: Jeff King <peff@peff.net>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
builtin/merge.c
t/t7600-merge.sh

index 2cbce56f8da9f7027cc95ad98540084b0ee98111..ecd7a897d05fa9be0730cbacb170b0c76331dedd 100644 (file)
@@ -1372,7 +1372,7 @@ int cmd_merge(int argc,
        struct commit_list *common = NULL;
        const char *best_strategy = NULL, *wt_strategy = NULL;
        struct commit_list *remoteheads = NULL, *p;
-       void *branch_to_free;
+       void *branch_to_free, *argv_to_free = NULL;
        int orig_argc = argc;
        int merge_log_config = -1;
 
@@ -1516,8 +1516,10 @@ int cmd_merge(int argc,
                option_commit = 1;
 
        if (!argc) {
-               if (default_to_upstream)
+               if (default_to_upstream) {
                        argc = setup_with_upstream(&argv);
+                       argv_to_free = argv;
+               }
                else
                        die(_("No commit specified and merge.defaultToUpstream not set."));
        } else if (argc == 1 && !strcmp(argv[0], "-")) {
@@ -1885,6 +1887,7 @@ done:
        }
        strbuf_release(&buf);
        free(branch_to_free);
+       free(argv_to_free);
        free(pull_twohead);
        free(pull_octopus);
        discard_index(the_repository->index);
index 9838094b66ac393e550eaabba7201ff987028a98..b3a2164e0836cd57b1ecf84aafcde9f8c5592e8a 100755 (executable)
@@ -1165,4 +1165,21 @@ test_expect_success 'suggested names are not ambiguous' '
        grep remotes/origin/not-local stderr
 '
 
+test_expect_success 'merge with no argument defaults to upstream' '
+       test_when_finished "rm -rf upstream downstream" &&
+       git init upstream &&
+       (
+               cd upstream &&
+               test_commit one &&
+               test_commit two
+       ) &&
+       git clone upstream downstream &&
+       (
+               cd downstream &&
+               git reset --hard HEAD^ &&
+               git merge &&
+               test_cmp_rev origin/main HEAD
+       )
+'
+
 test_done