Adds copy branch option available using -c or -C (forcefully). Includes a lot of function renames and their signature changes in order to introduce a new function parameter - flag 'copy' which determines whether those functions should do operation copy or move. Additionally, this changes a lot of other files wherever the renamed functions were used. By default copy=0 is passed at all those places so that they keep behaving the way they were, before these changes. Signed-off-by: Sahil Dua <sahildua2305@xxxxxxxxx> --- builtin/branch.c | 48 +++++++++++++++++++++++++++++++---------------- builtin/config.c | 4 ++-- builtin/remote.c | 6 +++--- cache.h | 4 ++-- config.c | 6 +++--- refs.c | 10 +++++----- refs.h | 7 ++++--- refs/files-backend.c | 21 ++++++++++++++------- refs/refs-internal.h | 6 +++--- submodule.c | 2 +- t/helper/test-ref-store.c | 2 +- 11 files changed, 70 insertions(+), 46 deletions(-) diff --git a/builtin/branch.c b/builtin/branch.c index 83fcda43dceec..16d01a100cbb9 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -27,6 +27,7 @@ static const char * const builtin_branch_usage[] = { N_("git branch [<options>] [-l] [-f] <branch-name> [<start-point>]"), N_("git branch [<options>] [-r] (-d | -D) <branch-name>..."), N_("git branch [<options>] (-m | -M) [<old-branch>] <new-branch>"), + N_("git branch [<options>] (-c | -C) [<old-branch>] <new-branch>"), N_("git branch [<options>] [-r | -a] [--points-at]"), N_("git branch [<options>] [-r | -a] [--format]"), NULL @@ -175,7 +176,7 @@ static void delete_branch_config(const char *branchname) { struct strbuf buf = STRBUF_INIT; strbuf_addf(&buf, "branch.%s", branchname); - if (git_config_rename_section(buf.buf, NULL) < 0) + if (git_config_copy_or_rename_section(buf.buf, NULL) < 0) warning(_("Update of config-file failed")); strbuf_release(&buf); } @@ -449,7 +450,7 @@ static void reject_rebase_or_bisect_branch(const char *target) free_worktrees(worktrees); } -static void rename_branch(const char *oldname, const char *newname, int force) +static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, int force) { struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT; struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT; @@ -457,7 +458,8 @@ static void rename_branch(const char *oldname, const char *newname, int force) int clobber_head_ok; if (!oldname) - die(_("cannot rename the current branch while not on any.")); + die(_("cannot %s the current branch while not on any."), + (copy ? "copy" : "rename")); if (strbuf_check_branch_ref(&oldref, oldname)) { /* @@ -480,17 +482,19 @@ static void rename_branch(const char *oldname, const char *newname, int force) reject_rebase_or_bisect_branch(oldref.buf); - strbuf_addf(&logmsg, "Branch: renamed %s to %s", - oldref.buf, newref.buf); + strbuf_addf(&logmsg, "Branch: %s %s to %s", + (copy ? "copied" : "renamed"), oldref.buf, newref.buf); - if (rename_ref(oldref.buf, newref.buf, logmsg.buf)) - die(_("Branch rename failed")); + if (copy_or_rename_ref(oldref.buf, newref.buf, logmsg.buf, copy)) + die(_("Branch %s failed"), (copy ? "copy" : "rename")); if (recovery) - warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11); + warning(_("%s a misnamed branch '%s' away"), + (copy ? "copied" : "renamed"), oldref.buf + 11); if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) - die(_("Branch renamed to %s, but HEAD is not updated!"), newname); + die(_("Branch %s to %s, but HEAD is not updated!"), + (copy ? "copied" : "renamed"), newname); strbuf_release(&logmsg); @@ -498,8 +502,9 @@ static void rename_branch(const char *oldname, const char *newname, int force) strbuf_release(&oldref); strbuf_addf(&newsection, "branch.%s", newref.buf + 11); strbuf_release(&newref); - if (git_config_rename_section(oldsection.buf, newsection.buf) < 0) - die(_("Branch is renamed, but update of config-file failed")); + if (git_config_copy_or_rename_section(oldsection.buf, newsection.buf) < 0) + die(_("Branch is %s, but update of config-file failed"), + (copy ? "copied" : "renamed")); strbuf_release(&oldsection); strbuf_release(&newsection); } @@ -537,7 +542,7 @@ static int edit_branch_description(const char *branch_name) int cmd_branch(int argc, const char **argv, const char *prefix) { - int delete = 0, rename = 0, force = 0, list = 0; + int delete = 0, rename = 0, copy = 0, force = 0, list = 0; int reflog = 0, edit_description = 0; int quiet = 0, unset_upstream = 0; const char *new_upstream = NULL; @@ -574,6 +579,8 @@ int cmd_branch(int argc, const char **argv, const char *prefix) OPT_BIT('D', NULL, &delete, N_("delete branch (even if not merged)"), 2), OPT_BIT('m', "move", &rename, N_("move/rename a branch and its reflog"), 1), OPT_BIT('M', NULL, &rename, N_("move/rename a branch, even if target exists"), 2), + OPT_BIT('c', NULL, ©, N_("copy a branch and its reflog"), 1), + OPT_BIT('C', NULL, ©, N_("copy a branch, even if target exists"), 2), OPT_BOOL(0, "list", &list, N_("list branch names")), OPT_BOOL('l', "create-reflog", &reflog, N_("create the branch's reflog")), OPT_BOOL(0, "edit-description", &edit_description, @@ -617,14 +624,14 @@ int cmd_branch(int argc, const char **argv, const char *prefix) argc = parse_options(argc, argv, prefix, options, builtin_branch_usage, 0); - if (!delete && !rename && !edit_description && !new_upstream && !unset_upstream && argc == 0) + if (!delete && !rename && !copy && !edit_description && !new_upstream && !unset_upstream && argc == 0) list = 1; if (filter.with_commit || filter.merge != REF_FILTER_MERGED_NONE || filter.points_at.nr || filter.no_commit) list = 1; - if (!!delete + !!rename + !!new_upstream + + if (!!delete + !!rename + !!copy + !!new_upstream + list + unset_upstream > 1) usage_with_options(builtin_branch_usage, options); @@ -696,13 +703,22 @@ int cmd_branch(int argc, const char **argv, const char *prefix) if (edit_branch_description(branch_name)) return 1; + } else if (copy) { + if (!argc) + die(_("branch name required")); + else if (argc == 1) + copy_or_rename_branch(head, argv[0], 1, copy > 1); + else if (argc == 2) + copy_or_rename_branch(argv[0], argv[1], 1, copy > 1); + else + die(_("too many branches for a copy operation")); } else if (rename) { if (!argc) die(_("branch name required")); else if (argc == 1) - rename_branch(head, argv[0], rename > 1); + copy_or_rename_branch(head, argv[0], 0, rename > 1); else if (argc == 2) - rename_branch(argv[0], argv[1], rename > 1); + copy_or_rename_branch(argv[0], argv[1], 0, rename > 1); else die(_("too many branches for a rename operation")); } else if (new_upstream) { diff --git a/builtin/config.c b/builtin/config.c index 7f6c25d4d95b3..c72972d731bd1 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -693,7 +693,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) int ret; check_write(); check_argc(argc, 2, 2); - ret = git_config_rename_section_in_file(given_config_source.file, + ret = git_config_copy_or_rename_section_in_file(given_config_source.file, argv[0], argv[1]); if (ret < 0) return ret; @@ -704,7 +704,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) int ret; check_write(); check_argc(argc, 1, 1); - ret = git_config_rename_section_in_file(given_config_source.file, + ret = git_config_copy_or_rename_section_in_file(given_config_source.file, argv[0], NULL); if (ret < 0) return ret; diff --git a/builtin/remote.c b/builtin/remote.c index addf97ad29343..ade748044b5ab 100644 --- a/builtin/remote.c +++ b/builtin/remote.c @@ -635,7 +635,7 @@ static int mv(int argc, const char **argv) strbuf_reset(&buf); strbuf_addf(&buf, "remote.%s", rename.old); strbuf_addf(&buf2, "remote.%s", rename.new); - if (git_config_rename_section(buf.buf, buf2.buf) < 1) + if (git_config_copy_or_rename_section(buf.buf, buf2.buf) < 1) return error(_("Could not rename config section '%s' to '%s'"), buf.buf, buf2.buf); @@ -706,7 +706,7 @@ static int mv(int argc, const char **argv) strbuf_reset(&buf2); strbuf_addf(&buf2, "remote: renamed %s to %s", item->string, buf.buf); - if (rename_ref(item->string, buf.buf, buf2.buf)) + if (copy_or_rename_ref(item->string, buf.buf, buf2.buf, 0)) die(_("renaming '%s' failed"), item->string); } for (i = 0; i < remote_branches.nr; i++) { @@ -804,7 +804,7 @@ static int rm(int argc, const char **argv) if (!result) { strbuf_addf(&buf, "remote.%s", remote->name); - if (git_config_rename_section(buf.buf, NULL) < 1) + if (git_config_copy_or_rename_section(buf.buf, NULL) < 1) return error(_("Could not remove config section '%s'"), buf.buf); } diff --git a/cache.h b/cache.h index ae4c45d379d5b..b2b043d3505ba 100644 --- a/cache.h +++ b/cache.h @@ -1933,8 +1933,8 @@ extern int git_config_set_multivar_gently(const char *, const char *, const char extern void git_config_set_multivar(const char *, const char *, const char *, int); extern int git_config_set_multivar_in_file_gently(const char *, const char *, const char *, const char *, int); extern void git_config_set_multivar_in_file(const char *, const char *, const char *, const char *, int); -extern int git_config_rename_section(const char *, const char *); -extern int git_config_rename_section_in_file(const char *, const char *, const char *); +extern int git_config_copy_or_rename_section(const char *, const char *); +extern int git_config_copy_or_rename_section_in_file(const char *, const char *, const char *); extern const char *git_etc_gitconfig(void); extern int git_env_bool(const char *, int); extern unsigned long git_env_ulong(const char *, unsigned long); diff --git a/config.c b/config.c index 146cb3452adab..78cf1ffac043e 100644 --- a/config.c +++ b/config.c @@ -2629,7 +2629,7 @@ static int section_name_is_ok(const char *name) } /* if new_name == NULL, the section is removed instead */ -int git_config_rename_section_in_file(const char *config_filename, +int git_config_copy_or_rename_section_in_file(const char *config_filename, const char *old_name, const char *new_name) { int ret = 0, remove = 0; @@ -2733,9 +2733,9 @@ int git_config_rename_section_in_file(const char *config_filename, return ret; } -int git_config_rename_section(const char *old_name, const char *new_name) +int git_config_copy_or_rename_section(const char *old_name, const char *new_name) { - return git_config_rename_section_in_file(NULL, old_name, new_name); + return git_config_copy_or_rename_section_in_file(NULL, old_name, new_name); } /* diff --git a/refs.c b/refs.c index 8af9641aa17e6..f8fb2577dfa9c 100644 --- a/refs.c +++ b/refs.c @@ -1907,13 +1907,13 @@ int delete_refs(struct string_list *refnames, unsigned int flags) return refs_delete_refs(get_main_ref_store(), refnames, flags); } -int refs_rename_ref(struct ref_store *refs, const char *oldref, - const char *newref, const char *logmsg) +int refs_copy_or_rename_ref(struct ref_store *refs, const char *oldref, + const char *newref, const char *logmsg, int copy) { - return refs->be->rename_ref(refs, oldref, newref, logmsg); + return refs->be->copy_or_rename_ref(refs, oldref, newref, logmsg, copy); } -int rename_ref(const char *oldref, const char *newref, const char *logmsg) +int copy_or_rename_ref(const char *oldref, const char *newref, const char *logmsg, int copy) { - return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg); + return refs_copy_or_rename_ref(get_main_ref_store(), oldref, newref, logmsg, copy); } diff --git a/refs.h b/refs.h index 685a979a0eb70..febdb09541813 100644 --- a/refs.h +++ b/refs.h @@ -394,9 +394,10 @@ const char *prettify_refname(const char *refname); char *shorten_unambiguous_ref(const char *refname, int strict); /** rename ref, return 0 on success **/ -int refs_rename_ref(struct ref_store *refs, const char *oldref, - const char *newref, const char *logmsg); -int rename_ref(const char *oldref, const char *newref, const char *logmsg); +int refs_copy_or_rename_ref(struct ref_store *refs, const char *oldref, + const char *newref, const char *logmsg, int copy); +int copy_or_rename_ref(const char *oldref, const char *newref, + const char *logmsg, int copy); int refs_create_symref(struct ref_store *refs, const char *refname, const char *target, const char *logmsg); diff --git a/refs/files-backend.c b/refs/files-backend.c index cb1f528cbeec4..670cc00d3f3e3 100644 --- a/refs/files-backend.c +++ b/refs/files-backend.c @@ -1703,9 +1703,9 @@ static int commit_ref_update(struct files_ref_store *refs, const struct object_id *oid, const char *logmsg, struct strbuf *err); -static int files_rename_ref(struct ref_store *ref_store, +static int files_copy_or_rename_ref(struct ref_store *ref_store, const char *oldrefname, const char *newrefname, - const char *logmsg) + const char *logmsg, int copy) { struct files_ref_store *refs = files_downcast(ref_store, REF_STORE_WRITE, "rename_ref"); @@ -1746,14 +1746,21 @@ static int files_rename_ref(struct ref_store *ref_store, goto out; } - if (log && rename(sb_oldref.buf, tmp_renamed_log.buf)) { + if (!copy && log && rename(sb_oldref.buf, tmp_renamed_log.buf)) { ret = error("unable to move logfile logs/%s to logs/"TMP_RENAMED_LOG": %s", oldrefname, strerror(errno)); goto out; } - if (refs_delete_ref(&refs->base, logmsg, oldrefname, - orig_oid.hash, REF_NODEREF)) { + // TODO: merge this block with the rename one above + if (copy && log && copy_file(tmp_renamed_log.buf, sb_oldref.buf, 0644)) { + ret = error("unable to copy logfile logs/%s to logs/"TMP_RENAMED_LOG": %s", + oldrefname, strerror(errno)); + goto out; + } + + if (!copy && refs_delete_ref(&refs->base, logmsg, oldrefname, + orig_sha1, REF_NODEREF)) { error("unable to delete old %s", oldrefname); goto rollback; } @@ -1765,7 +1772,7 @@ static int files_rename_ref(struct ref_store *ref_store, * the safety anyway; we want to delete the reference whatever * its current value. */ - if (!refs_read_ref_full(&refs->base, newrefname, + if (!copy && !refs_read_ref_full(&refs->base, newrefname, RESOLVE_REF_READING | RESOLVE_REF_NO_RECURSE, oid.hash, NULL) && refs_delete_ref(&refs->base, NULL, newrefname, @@ -3318,7 +3325,7 @@ struct ref_storage_be refs_be_files = { files_peel_ref, files_create_symref, files_delete_refs, - files_rename_ref, + files_copy_or_rename_ref, files_ref_iterator_begin, files_read_raw_ref, diff --git a/refs/refs-internal.h b/refs/refs-internal.h index b6b291cf00e5c..91d59b01fb570 100644 --- a/refs/refs-internal.h +++ b/refs/refs-internal.h @@ -510,9 +510,9 @@ typedef int create_symref_fn(struct ref_store *ref_store, const char *logmsg); typedef int delete_refs_fn(struct ref_store *ref_store, struct string_list *refnames, unsigned int flags); -typedef int rename_ref_fn(struct ref_store *ref_store, +typedef int copy_or_rename_ref_fn(struct ref_store *ref_store, const char *oldref, const char *newref, - const char *logmsg); + const char *logmsg, int copy); /* * Iterate over the references in the specified ref_store that are @@ -606,7 +606,7 @@ struct ref_storage_be { peel_ref_fn *peel_ref; create_symref_fn *create_symref; delete_refs_fn *delete_refs; - rename_ref_fn *rename_ref; + copy_or_rename_ref_fn *copy_or_rename_ref; ref_iterator_begin_fn *iterator_begin; read_raw_ref_fn *read_raw_ref; diff --git a/submodule.c b/submodule.c index bf5a93d16fb71..d93f366be31c6 100644 --- a/submodule.c +++ b/submodule.c @@ -107,7 +107,7 @@ int remove_path_from_gitmodules(const char *path) } strbuf_addstr(§, "submodule."); strbuf_addstr(§, submodule->name); - if (git_config_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) { + if (git_config_copy_or_rename_section_in_file(".gitmodules", sect.buf, NULL) < 0) { /* Maybe the user already did that, don't error out here */ warning(_("Could not remove .gitmodules entry for %s"), path); strbuf_release(§); diff --git a/t/helper/test-ref-store.c b/t/helper/test-ref-store.c index fba85e7da58fb..bfa031d77c8f0 100644 --- a/t/helper/test-ref-store.c +++ b/t/helper/test-ref-store.c @@ -107,7 +107,7 @@ static int cmd_rename_ref(struct ref_store *refs, const char **argv) const char *newref = notnull(*argv++, "newref"); const char *logmsg = *argv++; - return refs_rename_ref(refs, oldref, newref, logmsg); + return refs_copy_or_rename_ref(refs, oldref, newref, logmsg, 0); } static int each_ref(const char *refname, const struct object_id *oid, -- https://github.com/git/git/pull/363