]> git.ipfire.org Git - thirdparty/git.git/blobdiff - builtin/checkout.c
checkout: fix "branch info" memory leaks
[thirdparty/git.git] / builtin / checkout.c
index cbf73b8c9f65ae2fdd1d96265bd4972aeaa9bd68..43d0275187fc8fcac9f7106fb511b08f527b488e 100644 (file)
@@ -91,8 +91,8 @@ struct checkout_opts {
 };
 
 struct branch_info {
-       const char *name; /* The short name used */
-       const char *path; /* The full name of a real branch */
+       char *name; /* The short name used */
+       char *path; /* The full name of a real branch */
        struct commit *commit; /* The named commit */
        char *refname; /* The full name of the ref being checked out. */
        struct object_id oid; /* The object ID of the commit being checked out. */
@@ -103,6 +103,14 @@ struct branch_info {
        char *checkout;
 };
 
+static void branch_info_release(struct branch_info *info)
+{
+       free(info->name);
+       free(info->path);
+       free(info->refname);
+       free(info->checkout);
+}
+
 static int post_checkout_hook(struct commit *old_commit, struct commit *new_commit,
                              int changed)
 {
@@ -688,9 +696,12 @@ static void setup_branch_path(struct branch_info *branch)
                repo_get_oid_committish(the_repository, branch->name, &branch->oid);
 
        strbuf_branchname(&buf, branch->name, INTERPRET_BRANCH_LOCAL);
-       if (strcmp(buf.buf, branch->name))
+       if (strcmp(buf.buf, branch->name)) {
+               free(branch->name);
                branch->name = xstrdup(buf.buf);
+       }
        strbuf_splice(&buf, 0, 0, "refs/heads/", 11);
+       free(branch->path);
        branch->path = strbuf_detach(&buf, NULL);
 }
 
@@ -894,7 +905,9 @@ static void update_refs_for_switch(const struct checkout_opts *opts,
                                      opts->new_branch_log,
                                      opts->quiet,
                                      opts->track);
-               new_branch_info->name = opts->new_branch;
+               free(new_branch_info->name);
+               free(new_branch_info->refname);
+               new_branch_info->name = xstrdup(opts->new_branch);
                setup_branch_path(new_branch_info);
        }
 
@@ -1062,8 +1075,7 @@ static int switch_branches(const struct checkout_opts *opts,
                           struct branch_info *new_branch_info)
 {
        int ret = 0;
-       struct branch_info old_branch_info;
-       void *path_to_free;
+       struct branch_info old_branch_info = { 0 };
        struct object_id rev;
        int flag, writeout_error = 0;
        int do_merge = 1;
@@ -1071,25 +1083,32 @@ static int switch_branches(const struct checkout_opts *opts,
        trace2_cmd_mode("branch");
 
        memset(&old_branch_info, 0, sizeof(old_branch_info));
-       old_branch_info.path = path_to_free = resolve_refdup("HEAD", 0, &rev, &flag);
+       old_branch_info.path = resolve_refdup("HEAD", 0, &rev, &flag);
        if (old_branch_info.path)
                old_branch_info.commit = lookup_commit_reference_gently(the_repository, &rev, 1);
        if (!(flag & REF_ISSYMREF))
-               old_branch_info.path = NULL;
+               FREE_AND_NULL(old_branch_info.path);
 
-       if (old_branch_info.path)
-               skip_prefix(old_branch_info.path, "refs/heads/", &old_branch_info.name);
+       if (old_branch_info.path) {
+               const char *const prefix = "refs/heads/";
+               const char *p;
+               if (skip_prefix(old_branch_info.path, prefix, &p))
+                       old_branch_info.name = xstrdup(p);
+               else
+                       BUG("should be able to skip past '%s' in '%s'!",
+                           prefix, old_branch_info.path);
+       }
 
        if (opts->new_orphan_branch && opts->orphan_from_empty_tree) {
                if (new_branch_info->name)
                        BUG("'switch --orphan' should never accept a commit as starting point");
                new_branch_info->commit = NULL;
-               new_branch_info->name = "(empty)";
+               new_branch_info->name = xstrdup("(empty)");
                do_merge = 1;
        }
 
        if (!new_branch_info->name) {
-               new_branch_info->name = "HEAD";
+               new_branch_info->name = xstrdup("HEAD");
                new_branch_info->commit = old_branch_info.commit;
                if (!new_branch_info->commit)
                        die(_("You are on a branch yet to be born"));
@@ -1102,7 +1121,7 @@ static int switch_branches(const struct checkout_opts *opts,
        if (do_merge) {
                ret = merge_working_tree(opts, &old_branch_info, new_branch_info, &writeout_error);
                if (ret) {
-                       free(path_to_free);
+                       branch_info_release(&old_branch_info);
                        return ret;
                }
        }
@@ -1113,7 +1132,8 @@ static int switch_branches(const struct checkout_opts *opts,
        update_refs_for_switch(opts, &old_branch_info, new_branch_info);
 
        ret = post_checkout_hook(old_branch_info.commit, new_branch_info->commit, 1);
-       free(path_to_free);
+       branch_info_release(&old_branch_info);
+
        return ret || writeout_error;
 }
 
@@ -1145,16 +1165,15 @@ static void setup_new_branch_info_and_source_tree(
        struct tree **source_tree = &opts->source_tree;
        struct object_id branch_rev;
 
-       new_branch_info->name = arg;
+       new_branch_info->name = xstrdup(arg);
        setup_branch_path(new_branch_info);
 
        if (!check_refname_format(new_branch_info->path, 0) &&
            !read_ref(new_branch_info->path, &branch_rev))
                oidcpy(rev, &branch_rev);
-       else {
-               free((char *)new_branch_info->path);
-               new_branch_info->path = NULL; /* not an existing branch */
-       }
+       else
+               /* not an existing branch */
+               FREE_AND_NULL(new_branch_info->path);
 
        new_branch_info->commit = lookup_commit_reference_gently(the_repository, rev, 1);
        if (!new_branch_info->commit) {
@@ -1574,12 +1593,11 @@ static char cb_option = 'b';
 
 static int checkout_main(int argc, const char **argv, const char *prefix,
                         struct checkout_opts *opts, struct option *options,
-                        const char * const usagestr[])
+                        const char * const usagestr[],
+                        struct branch_info *new_branch_info)
 {
-       struct branch_info new_branch_info;
        int parseopt_flags = 0;
 
-       memset(&new_branch_info, 0, sizeof(new_branch_info));
        opts->overwrite_ignore = 1;
        opts->prefix = prefix;
        opts->show_progress = -1;
@@ -1688,7 +1706,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                        opts->track == BRANCH_TRACK_UNSPECIFIED &&
                        !opts->new_branch;
                int n = parse_branchname_arg(argc, argv, dwim_ok,
-                                            &new_branch_info, opts, &rev);
+                                            new_branch_info, opts, &rev);
                argv += n;
                argc -= n;
        } else if (!opts->accept_ref && opts->from_treeish) {
@@ -1697,7 +1715,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                if (get_oid_mb(opts->from_treeish, &rev))
                        die(_("could not resolve %s"), opts->from_treeish);
 
-               setup_new_branch_info_and_source_tree(&new_branch_info,
+               setup_new_branch_info_and_source_tree(new_branch_info,
                                                      opts, &rev,
                                                      opts->from_treeish);
 
@@ -1717,7 +1735,7 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                 * Try to give more helpful suggestion.
                 * new_branch && argc > 1 will be caught later.
                 */
-               if (opts->new_branch && argc == 1 && !new_branch_info.commit)
+               if (opts->new_branch && argc == 1 && !new_branch_info->commit)
                        die(_("'%s' is not a commit and a branch '%s' cannot be created from it"),
                                argv[0], opts->new_branch);
 
@@ -1766,11 +1784,10 @@ static int checkout_main(int argc, const char **argv, const char *prefix,
                strbuf_release(&buf);
        }
 
-       UNLEAK(opts);
        if (opts->patch_mode || opts->pathspec.nr)
-               return checkout_paths(opts, &new_branch_info);
+               return checkout_paths(opts, new_branch_info);
        else
-               return checkout_branch(opts, &new_branch_info);
+               return checkout_branch(opts, new_branch_info);
 }
 
 int cmd_checkout(int argc, const char **argv, const char *prefix)
@@ -1789,6 +1806,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
        int ret;
+       struct branch_info new_branch_info = { 0 };
 
        memset(&opts, 0, sizeof(opts));
        opts.dwim_new_local_branch = 1;
@@ -1819,7 +1837,9 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
        options = add_checkout_path_options(&opts, options);
 
        ret = checkout_main(argc, argv, prefix, &opts,
-                           options, checkout_usage);
+                           options, checkout_usage, &new_branch_info);
+       branch_info_release(&new_branch_info);
+       clear_pathspec(&opts.pathspec);
        FREE_AND_NULL(options);
        return ret;
 }
@@ -1840,6 +1860,7 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
        int ret;
+       struct branch_info new_branch_info = { 0 };
 
        memset(&opts, 0, sizeof(opts));
        opts.dwim_new_local_branch = 1;
@@ -1859,7 +1880,8 @@ int cmd_switch(int argc, const char **argv, const char *prefix)
        cb_option = 'c';
 
        ret = checkout_main(argc, argv, prefix, &opts,
-                           options, switch_branch_usage);
+                           options, switch_branch_usage, &new_branch_info);
+       branch_info_release(&new_branch_info);
        FREE_AND_NULL(options);
        return ret;
 }
@@ -1881,6 +1903,7 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
                OPT_END()
        };
        int ret;
+       struct branch_info new_branch_info = { 0 };
 
        memset(&opts, 0, sizeof(opts));
        opts.accept_ref = 0;
@@ -1896,7 +1919,8 @@ int cmd_restore(int argc, const char **argv, const char *prefix)
        options = add_checkout_path_options(&opts, options);
 
        ret = checkout_main(argc, argv, prefix, &opts,
-                           options, restore_usage);
+                           options, restore_usage, &new_branch_info);
+       branch_info_release(&new_branch_info);
        FREE_AND_NULL(options);
        return ret;
 }