branch: add a --copy (-c) option to go with --move (-m)
Add the ability to --copy a branch and its reflog and configuration, this uses the same underlying machinery as the --move (-m) option except the reflog and configuration is copied instead of being moved. This is useful for e.g. copying a topic branch to a new version, e.g. work to work-2 after submitting the work topic to the list, while preserving all the tracking info and other configuration that goes with the branch, and unlike --move keeping the other already-submitted branch around for reference. Like --move, when the source branch is the currently checked out branch the HEAD is moved to the destination branch. In the case of --move we don't really have a choice (other than remaining on a detached HEAD) and in order to keep the functionality consistent, we are doing it in similar way for --copy too. The most common usage of this feature is expected to be moving to a new topic branch which is a copy of the current one, in that case moving to the target branch is what the user wants, and doesn't unexpectedly behave differently than --move would. One outstanding caveat of this implementation is that: git checkout maint && git checkout master && git branch -c topic && git checkout - Will check out 'maint' instead of 'master'. This is because the @{-N} feature (or its -1 shorthand "-") relies on HEAD reflogs created by the checkout command, so in this case we'll checkout maint instead of master, as the user might expect. What to do about that is left to a future change. Helped-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com> Signed-off-by: Sahil Dua <sahildua2305@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:

committed by
Junio C Hamano

parent
c8b2cec09e
commit
52d59cc645
@ -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
|
||||
@ -449,15 +450,19 @@ 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;
|
||||
int recovery = 0;
|
||||
int clobber_head_ok;
|
||||
|
||||
if (!oldname)
|
||||
die(_("cannot rename the current branch while not on any."));
|
||||
if (!oldname) {
|
||||
if (copy)
|
||||
die(_("cannot copy the current branch while not on any."));
|
||||
else
|
||||
die(_("cannot rename the current branch while not on any."));
|
||||
}
|
||||
|
||||
if (strbuf_check_branch_ref(&oldref, oldname)) {
|
||||
/*
|
||||
@ -480,17 +485,33 @@ 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);
|
||||
if (copy)
|
||||
strbuf_addf(&logmsg, "Branch: copied %s to %s",
|
||||
oldref.buf, newref.buf);
|
||||
else
|
||||
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
|
||||
oldref.buf, newref.buf);
|
||||
|
||||
if (rename_ref(oldref.buf, newref.buf, logmsg.buf))
|
||||
if (!copy && rename_ref(oldref.buf, newref.buf, logmsg.buf))
|
||||
die(_("Branch rename failed"));
|
||||
if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
|
||||
die(_("Branch copy failed"));
|
||||
|
||||
if (recovery)
|
||||
warning(_("Renamed a misnamed branch '%s' away"), oldref.buf + 11);
|
||||
if (recovery) {
|
||||
if (copy)
|
||||
warning(_("Copied a misnamed branch '%s' away"),
|
||||
oldref.buf + 11);
|
||||
else
|
||||
warning(_("Renamed a misnamed branch '%s' away"),
|
||||
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);
|
||||
if (replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf)) {
|
||||
if (copy)
|
||||
die(_("Branch copied to %s, but HEAD is not updated!"), newname);
|
||||
else
|
||||
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
|
||||
}
|
||||
|
||||
strbuf_release(&logmsg);
|
||||
|
||||
@ -498,8 +519,10 @@ 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)
|
||||
if (!copy && git_config_rename_section(oldsection.buf, newsection.buf) < 0)
|
||||
die(_("Branch is renamed, but update of config-file failed"));
|
||||
if (copy && strcmp(oldname, newname) && git_config_copy_section(oldsection.buf, newsection.buf) < 0)
|
||||
die(_("Branch is copied, but update of config-file failed"));
|
||||
strbuf_release(&oldsection);
|
||||
strbuf_release(&newsection);
|
||||
}
|
||||
@ -537,7 +560,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 +597,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', "copy", ©, 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 +642,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);
|
||||
|
||||
@ -642,6 +667,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
||||
if (force) {
|
||||
delete *= 2;
|
||||
rename *= 2;
|
||||
copy *= 2;
|
||||
}
|
||||
|
||||
if (delete) {
|
||||
@ -696,13 +722,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) {
|
||||
|
Reference in New Issue
Block a user