зеркало из https://github.com/microsoft/git.git
Merge branch 'sd/branch-copy'
"git branch" learned "-c/-C" to create a new branch by copying an existing one. * sd/branch-copy: branch: fix "copy" to never touch HEAD branch: add a --copy (-c) option to go with --move (-m) branch: add test for -m renaming multiple config sections config: create a function to format section headers
This commit is contained in:
Коммит
3b48045c6c
|
@ -18,6 +18,7 @@ SYNOPSIS
|
|||
'git branch' (--set-upstream-to=<upstream> | -u <upstream>) [<branchname>]
|
||||
'git branch' --unset-upstream [<branchname>]
|
||||
'git branch' (-m | -M) [<oldbranch>] <newbranch>
|
||||
'git branch' (-c | -C) [<oldbranch>] <newbranch>
|
||||
'git branch' (-d | -D) [-r] <branchname>...
|
||||
'git branch' --edit-description [<branchname>]
|
||||
|
||||
|
@ -64,6 +65,10 @@ If <oldbranch> had a corresponding reflog, it is renamed to match
|
|||
renaming. If <newbranch> exists, -M must be used to force the rename
|
||||
to happen.
|
||||
|
||||
The `-c` and `-C` options have the exact same semantics as `-m` and
|
||||
`-M`, except instead of the branch being renamed it along with its
|
||||
config and reflog will be copied to a new name.
|
||||
|
||||
With a `-d` or `-D` option, `<branchname>` will be deleted. You may
|
||||
specify more than one branch for deletion. If the branch currently
|
||||
has a reflog then the reflog will also be deleted.
|
||||
|
@ -104,7 +109,7 @@ OPTIONS
|
|||
In combination with `-d` (or `--delete`), allow deleting the
|
||||
branch irrespective of its merged status. In combination with
|
||||
`-m` (or `--move`), allow renaming the branch even if the new
|
||||
branch name already exists.
|
||||
branch name already exists, the same applies for `-c` (or `--copy`).
|
||||
|
||||
-m::
|
||||
--move::
|
||||
|
@ -113,6 +118,13 @@ OPTIONS
|
|||
-M::
|
||||
Shortcut for `--move --force`.
|
||||
|
||||
-c::
|
||||
--copy::
|
||||
Copy a branch and the corresponding reflog.
|
||||
|
||||
-C::
|
||||
Shortcut for `--copy --force`.
|
||||
|
||||
--color[=<when>]::
|
||||
Color branches to highlight current, local, and
|
||||
remote-tracking branches.
|
||||
|
|
|
@ -28,6 +28,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
|
||||
|
@ -456,15 +457,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)) {
|
||||
/*
|
||||
|
@ -487,16 +492,29 @@ 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))
|
||||
if (!copy &&
|
||||
replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
|
||||
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
|
||||
|
||||
strbuf_release(&logmsg);
|
||||
|
@ -505,8 +523,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);
|
||||
}
|
||||
|
@ -544,7 +564,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;
|
||||
|
@ -581,6 +601,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,
|
||||
|
@ -624,14 +646,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);
|
||||
|
||||
|
@ -649,6 +671,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
|||
if (force) {
|
||||
delete *= 2;
|
||||
rename *= 2;
|
||||
copy *= 2;
|
||||
}
|
||||
|
||||
if (delete) {
|
||||
|
@ -703,13 +726,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) {
|
||||
|
|
114
config.c
114
config.c
|
@ -2292,11 +2292,10 @@ static int write_error(const char *filename)
|
|||
return 4;
|
||||
}
|
||||
|
||||
static ssize_t write_section(int fd, const char *key)
|
||||
static struct strbuf store_create_section(const char *key)
|
||||
{
|
||||
const char *dot;
|
||||
int i;
|
||||
ssize_t ret;
|
||||
struct strbuf sb = STRBUF_INIT;
|
||||
|
||||
dot = memchr(key, '.', store.baselen);
|
||||
|
@ -2312,7 +2311,15 @@ static ssize_t write_section(int fd, const char *key)
|
|||
strbuf_addf(&sb, "[%.*s]\n", store.baselen, key);
|
||||
}
|
||||
|
||||
ret = write_in_full(fd, sb.buf, sb.len);
|
||||
return sb;
|
||||
}
|
||||
|
||||
static ssize_t write_section(int fd, const char *key)
|
||||
{
|
||||
struct strbuf sb = store_create_section(key);
|
||||
ssize_t ret;
|
||||
|
||||
ret = write_in_full(fd, sb.buf, sb.len) == sb.len;
|
||||
strbuf_release(&sb);
|
||||
|
||||
return ret;
|
||||
|
@ -2743,8 +2750,8 @@ 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,
|
||||
const char *old_name, const char *new_name)
|
||||
static int git_config_copy_or_rename_section_in_file(const char *config_filename,
|
||||
const char *old_name, const char *new_name, int copy)
|
||||
{
|
||||
int ret = 0, remove = 0;
|
||||
char *filename_buf = NULL;
|
||||
|
@ -2753,6 +2760,7 @@ int git_config_rename_section_in_file(const char *config_filename,
|
|||
char buf[1024];
|
||||
FILE *config_file = NULL;
|
||||
struct stat st;
|
||||
struct strbuf copystr = STRBUF_INIT;
|
||||
|
||||
if (new_name && !section_name_is_ok(new_name)) {
|
||||
ret = error("invalid section name: %s", new_name);
|
||||
|
@ -2791,12 +2799,30 @@ int git_config_rename_section_in_file(const char *config_filename,
|
|||
while (fgets(buf, sizeof(buf), config_file)) {
|
||||
int i;
|
||||
int length;
|
||||
int is_section = 0;
|
||||
char *output = buf;
|
||||
for (i = 0; buf[i] && isspace(buf[i]); i++)
|
||||
; /* do nothing */
|
||||
if (buf[i] == '[') {
|
||||
/* it's a section */
|
||||
int offset = section_name_match(&buf[i], old_name);
|
||||
int offset;
|
||||
is_section = 1;
|
||||
|
||||
/*
|
||||
* When encountering a new section under -c we
|
||||
* need to flush out any section we're already
|
||||
* coping and begin anew. There might be
|
||||
* multiple [branch "$name"] sections.
|
||||
*/
|
||||
if (copystr.len > 0) {
|
||||
if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out;
|
||||
}
|
||||
strbuf_reset(©str);
|
||||
}
|
||||
|
||||
offset = section_name_match(&buf[i], old_name);
|
||||
if (offset > 0) {
|
||||
ret++;
|
||||
if (new_name == NULL) {
|
||||
|
@ -2804,25 +2830,29 @@ int git_config_rename_section_in_file(const char *config_filename,
|
|||
continue;
|
||||
}
|
||||
store.baselen = strlen(new_name);
|
||||
if (write_section(out_fd, new_name) < 0) {
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* We wrote out the new section, with
|
||||
* a newline, now skip the old
|
||||
* section's length
|
||||
*/
|
||||
output += offset + i;
|
||||
if (strlen(output) > 0) {
|
||||
if (!copy) {
|
||||
if (write_section(out_fd, new_name) < 0) {
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out;
|
||||
}
|
||||
/*
|
||||
* More content means there's
|
||||
* a declaration to put on the
|
||||
* next line; indent with a
|
||||
* tab
|
||||
* We wrote out the new section, with
|
||||
* a newline, now skip the old
|
||||
* section's length
|
||||
*/
|
||||
output -= 1;
|
||||
output[0] = '\t';
|
||||
output += offset + i;
|
||||
if (strlen(output) > 0) {
|
||||
/*
|
||||
* More content means there's
|
||||
* a declaration to put on the
|
||||
* next line; indent with a
|
||||
* tab
|
||||
*/
|
||||
output -= 1;
|
||||
output[0] = '\t';
|
||||
}
|
||||
} else {
|
||||
copystr = store_create_section(new_name);
|
||||
}
|
||||
}
|
||||
remove = 0;
|
||||
|
@ -2830,11 +2860,30 @@ int git_config_rename_section_in_file(const char *config_filename,
|
|||
if (remove)
|
||||
continue;
|
||||
length = strlen(output);
|
||||
|
||||
if (!is_section && copystr.len > 0) {
|
||||
strbuf_add(©str, output, length);
|
||||
}
|
||||
|
||||
if (write_in_full(out_fd, output, length) < 0) {
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Copy a trailing section at the end of the config, won't be
|
||||
* flushed by the usual "flush because we have a new section
|
||||
* logic in the loop above.
|
||||
*/
|
||||
if (copystr.len > 0) {
|
||||
if (write_in_full(out_fd, copystr.buf, copystr.len) != copystr.len) {
|
||||
ret = write_error(get_lock_file_path(lock));
|
||||
goto out;
|
||||
}
|
||||
strbuf_reset(©str);
|
||||
}
|
||||
|
||||
fclose(config_file);
|
||||
config_file = NULL;
|
||||
commit_and_out:
|
||||
|
@ -2850,11 +2899,30 @@ out_no_rollback:
|
|||
return ret;
|
||||
}
|
||||
|
||||
int git_config_rename_section_in_file(const char *config_filename,
|
||||
const char *old_name, const char *new_name)
|
||||
{
|
||||
return git_config_copy_or_rename_section_in_file(config_filename,
|
||||
old_name, new_name, 0);
|
||||
}
|
||||
|
||||
int git_config_rename_section(const char *old_name, const char *new_name)
|
||||
{
|
||||
return git_config_rename_section_in_file(NULL, old_name, new_name);
|
||||
}
|
||||
|
||||
int git_config_copy_section_in_file(const char *config_filename,
|
||||
const char *old_name, const char *new_name)
|
||||
{
|
||||
return git_config_copy_or_rename_section_in_file(config_filename,
|
||||
old_name, new_name, 1);
|
||||
}
|
||||
|
||||
int git_config_copy_section(const char *old_name, const char *new_name)
|
||||
{
|
||||
return git_config_copy_section_in_file(NULL, old_name, new_name);
|
||||
}
|
||||
|
||||
/*
|
||||
* Call this to report error for your variable that should not
|
||||
* get a boolean value (i.e. "[my] var" means "true").
|
||||
|
|
2
config.h
2
config.h
|
@ -70,6 +70,8 @@ extern int git_config_set_multivar_in_file_gently(const char *, const char *, co
|
|||
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_section(const char *, const char *);
|
||||
extern int git_config_copy_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);
|
||||
|
|
11
refs.c
11
refs.c
|
@ -2036,3 +2036,14 @@ int rename_ref(const char *oldref, const char *newref, const char *logmsg)
|
|||
{
|
||||
return refs_rename_ref(get_main_ref_store(), oldref, newref, logmsg);
|
||||
}
|
||||
|
||||
int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
|
||||
const char *newref, const char *logmsg)
|
||||
{
|
||||
return refs->be->copy_ref(refs, oldref, newref, logmsg);
|
||||
}
|
||||
|
||||
int copy_existing_ref(const char *oldref, const char *newref, const char *logmsg)
|
||||
{
|
||||
return refs_copy_existing_ref(get_main_ref_store(), oldref, newref, logmsg);
|
||||
}
|
||||
|
|
9
refs.h
9
refs.h
|
@ -442,7 +442,14 @@ 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 rename_ref(const char *oldref, const char *newref,
|
||||
const char *logmsg);
|
||||
|
||||
/** copy ref, return 0 on success **/
|
||||
int refs_copy_existing_ref(struct ref_store *refs, const char *oldref,
|
||||
const char *newref, const char *logmsg);
|
||||
int copy_existing_ref(const char *oldref, const char *newref,
|
||||
const char *logmsg);
|
||||
|
||||
int refs_create_symref(struct ref_store *refs, const char *refname,
|
||||
const char *target, const char *logmsg);
|
||||
|
|
|
@ -1258,9 +1258,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");
|
||||
|
@ -1292,8 +1292,12 @@ static int files_rename_ref(struct ref_store *ref_store,
|
|||
}
|
||||
|
||||
if (flag & REF_ISSYMREF) {
|
||||
ret = error("refname %s is a symbolic ref, renaming it is not supported",
|
||||
oldrefname);
|
||||
if (copy)
|
||||
ret = error("refname %s is a symbolic ref, copying it is not supported",
|
||||
oldrefname);
|
||||
else
|
||||
ret = error("refname %s is a symbolic ref, renaming it is not supported",
|
||||
oldrefname);
|
||||
goto out;
|
||||
}
|
||||
if (!refs_rename_ref_available(&refs->base, oldrefname, newrefname)) {
|
||||
|
@ -1301,13 +1305,19 @@ 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,
|
||||
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_oid.hash, REF_NODEREF)) {
|
||||
error("unable to delete old %s", oldrefname);
|
||||
goto rollback;
|
||||
|
@ -1320,7 +1330,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,
|
||||
|
@ -1351,7 +1361,10 @@ static int files_rename_ref(struct ref_store *ref_store,
|
|||
lock = lock_ref_sha1_basic(refs, newrefname, NULL, NULL, NULL,
|
||||
REF_NODEREF, NULL, &err);
|
||||
if (!lock) {
|
||||
error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
|
||||
if (copy)
|
||||
error("unable to copy '%s' to '%s': %s", oldrefname, newrefname, err.buf);
|
||||
else
|
||||
error("unable to rename '%s' to '%s': %s", oldrefname, newrefname, err.buf);
|
||||
strbuf_release(&err);
|
||||
goto rollback;
|
||||
}
|
||||
|
@ -1402,6 +1415,22 @@ static int files_rename_ref(struct ref_store *ref_store,
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int files_rename_ref(struct ref_store *ref_store,
|
||||
const char *oldrefname, const char *newrefname,
|
||||
const char *logmsg)
|
||||
{
|
||||
return files_copy_or_rename_ref(ref_store, oldrefname,
|
||||
newrefname, logmsg, 0);
|
||||
}
|
||||
|
||||
static int files_copy_ref(struct ref_store *ref_store,
|
||||
const char *oldrefname, const char *newrefname,
|
||||
const char *logmsg)
|
||||
{
|
||||
return files_copy_or_rename_ref(ref_store, oldrefname,
|
||||
newrefname, logmsg, 1);
|
||||
}
|
||||
|
||||
static int close_ref_gently(struct ref_lock *lock)
|
||||
{
|
||||
if (close_lock_file_gently(&lock->lk))
|
||||
|
@ -3064,6 +3093,7 @@ struct ref_storage_be refs_be_files = {
|
|||
files_create_symref,
|
||||
files_delete_refs,
|
||||
files_rename_ref,
|
||||
files_copy_ref,
|
||||
|
||||
files_ref_iterator_begin,
|
||||
files_read_raw_ref,
|
||||
|
|
|
@ -966,6 +966,13 @@ static int packed_rename_ref(struct ref_store *ref_store,
|
|||
die("BUG: packed reference store does not support renaming references");
|
||||
}
|
||||
|
||||
static int packed_copy_ref(struct ref_store *ref_store,
|
||||
const char *oldrefname, const char *newrefname,
|
||||
const char *logmsg)
|
||||
{
|
||||
die("BUG: packed reference store does not support copying references");
|
||||
}
|
||||
|
||||
static struct ref_iterator *packed_reflog_iterator_begin(struct ref_store *ref_store)
|
||||
{
|
||||
return empty_ref_iterator_begin();
|
||||
|
@ -1031,6 +1038,7 @@ struct ref_storage_be refs_be_packed = {
|
|||
packed_create_symref,
|
||||
packed_delete_refs,
|
||||
packed_rename_ref,
|
||||
packed_copy_ref,
|
||||
|
||||
packed_ref_iterator_begin,
|
||||
packed_read_raw_ref,
|
||||
|
|
|
@ -559,6 +559,9 @@ typedef int delete_refs_fn(struct ref_store *ref_store, const char *msg,
|
|||
typedef int rename_ref_fn(struct ref_store *ref_store,
|
||||
const char *oldref, const char *newref,
|
||||
const char *logmsg);
|
||||
typedef int copy_ref_fn(struct ref_store *ref_store,
|
||||
const char *oldref, const char *newref,
|
||||
const char *logmsg);
|
||||
|
||||
/*
|
||||
* Iterate over the references in `ref_store` whose names start with
|
||||
|
@ -657,6 +660,7 @@ struct ref_storage_be {
|
|||
create_symref_fn *create_symref;
|
||||
delete_refs_fn *delete_refs;
|
||||
rename_ref_fn *rename_ref;
|
||||
copy_ref_fn *copy_ref;
|
||||
|
||||
ref_iterator_begin_fn *iterator_begin;
|
||||
read_raw_ref_fn *read_raw_ref;
|
||||
|
|
|
@ -381,6 +381,262 @@ test_expect_success 'config information was renamed, too' '
|
|||
test_must_fail git config branch.s/s.dummy
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -m correctly renames multiple config sections' '
|
||||
test_when_finished "git checkout master" &&
|
||||
git checkout -b source master &&
|
||||
|
||||
# Assert that a config file with multiple config sections has
|
||||
# those sections preserved...
|
||||
cat >expect <<-\EOF &&
|
||||
branch.dest.key1=value1
|
||||
some.gar.b=age
|
||||
branch.dest.key2=value2
|
||||
EOF
|
||||
cat >config.branch <<\EOF &&
|
||||
;; Note the lack of -\EOF above & mixed indenting here. This is
|
||||
;; intentional, we are also testing that the formatting of copied
|
||||
;; sections is preserved.
|
||||
|
||||
;; Comment for source. Tabs
|
||||
[branch "source"]
|
||||
;; Comment for the source value
|
||||
key1 = value1
|
||||
;; Comment for some.gar. Spaces
|
||||
[some "gar"]
|
||||
;; Comment for the some.gar value
|
||||
b = age
|
||||
;; Comment for source, again. Mixed tabs/spaces.
|
||||
[branch "source"]
|
||||
;; Comment for the source value, again
|
||||
key2 = value2
|
||||
EOF
|
||||
cat config.branch >>.git/config &&
|
||||
git branch -m source dest &&
|
||||
git config -f .git/config -l | grep -F -e source -e dest -e some.gar >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# ...and that the comments for those sections are also
|
||||
# preserved.
|
||||
cat config.branch | sed "s/\"source\"/\"dest\"/" >expect &&
|
||||
sed -n -e "/Note the lack/,\$p" .git/config >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c dumps usage' '
|
||||
test_expect_code 128 git branch -c 2>err &&
|
||||
test_i18ngrep "branch name required" err
|
||||
'
|
||||
|
||||
test_expect_success 'git branch --copy dumps usage' '
|
||||
test_expect_code 128 git branch --copy 2>err &&
|
||||
test_i18ngrep "branch name required" err
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c d e should work' '
|
||||
git branch -l d &&
|
||||
git reflog exists refs/heads/d &&
|
||||
git config branch.d.dummy Hello &&
|
||||
git branch -c d e &&
|
||||
git reflog exists refs/heads/d &&
|
||||
git reflog exists refs/heads/e &&
|
||||
echo Hello >expect &&
|
||||
git config branch.e.dummy >actual &&
|
||||
test_cmp expect actual &&
|
||||
echo Hello >expect &&
|
||||
git config branch.d.dummy >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'git branch --copy is a synonym for -c' '
|
||||
git branch -l copy &&
|
||||
git reflog exists refs/heads/copy &&
|
||||
git config branch.copy.dummy Hello &&
|
||||
git branch --copy copy copy-to &&
|
||||
git reflog exists refs/heads/copy &&
|
||||
git reflog exists refs/heads/copy-to &&
|
||||
echo Hello >expect &&
|
||||
git config branch.copy.dummy >actual &&
|
||||
test_cmp expect actual &&
|
||||
echo Hello >expect &&
|
||||
git config branch.copy-to.dummy >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c ee ef should copy ee to create branch ef' '
|
||||
git checkout -b ee &&
|
||||
git reflog exists refs/heads/ee &&
|
||||
git config branch.ee.dummy Hello &&
|
||||
git branch -c ee ef &&
|
||||
git reflog exists refs/heads/ee &&
|
||||
git reflog exists refs/heads/ef &&
|
||||
test $(git config branch.ee.dummy) = Hello &&
|
||||
test $(git config branch.ef.dummy) = Hello &&
|
||||
test $(git rev-parse --abbrev-ref HEAD) = ee
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c f/f g/g should work' '
|
||||
git branch -l f/f &&
|
||||
git reflog exists refs/heads/f/f &&
|
||||
git config branch.f/f.dummy Hello &&
|
||||
git branch -c f/f g/g &&
|
||||
git reflog exists refs/heads/f/f &&
|
||||
git reflog exists refs/heads/g/g &&
|
||||
test $(git config branch.f/f.dummy) = Hello &&
|
||||
test $(git config branch.g/g.dummy) = Hello
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c m2 m2 should work' '
|
||||
git branch -l m2 &&
|
||||
git reflog exists refs/heads/m2 &&
|
||||
git config branch.m2.dummy Hello &&
|
||||
git branch -c m2 m2 &&
|
||||
git reflog exists refs/heads/m2 &&
|
||||
test $(git config branch.m2.dummy) = Hello
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c zz zz/zz should fail' '
|
||||
git branch -l zz &&
|
||||
git reflog exists refs/heads/zz &&
|
||||
test_must_fail git branch -c zz zz/zz
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c b/b b should fail' '
|
||||
git branch -l b/b &&
|
||||
test_must_fail git branch -c b/b b
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C o/q o/p should work when o/p exists' '
|
||||
git branch -l o/q &&
|
||||
git reflog exists refs/heads/o/q &&
|
||||
git reflog exists refs/heads/o/p &&
|
||||
git branch -C o/q o/p
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c -f o/q o/p should work when o/p exists' '
|
||||
git reflog exists refs/heads/o/q &&
|
||||
git reflog exists refs/heads/o/p &&
|
||||
git branch -c -f o/q o/p
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c qq rr/qq should fail when r exists' '
|
||||
git branch qq &&
|
||||
git branch rr &&
|
||||
test_must_fail git branch -c qq rr/qq
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C b1 b2 should fail when b2 is checked out' '
|
||||
git branch b1 &&
|
||||
git checkout -b b2 &&
|
||||
test_must_fail git branch -C b1 b2
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C c1 c2 should succeed when c1 is checked out' '
|
||||
git checkout -b c1 &&
|
||||
git branch c2 &&
|
||||
git branch -C c1 c2 &&
|
||||
test $(git rev-parse --abbrev-ref HEAD) = c1
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C c1 c2 should never touch HEAD' '
|
||||
msg="Branch: copied refs/heads/c1 to refs/heads/c2" &&
|
||||
! grep "$msg$" .git/logs/HEAD
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C master should work when master is checked out' '
|
||||
git checkout master &&
|
||||
git branch -C master
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C master master should work when master is checked out' '
|
||||
git checkout master &&
|
||||
git branch -C master master
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C master5 master5 should work when master is checked out' '
|
||||
git checkout master &&
|
||||
git branch master5 &&
|
||||
git branch -C master5 master5
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -C ab cd should overwrite existing config for cd' '
|
||||
git branch -l cd &&
|
||||
git reflog exists refs/heads/cd &&
|
||||
git config branch.cd.dummy CD &&
|
||||
git branch -l ab &&
|
||||
git reflog exists refs/heads/ab &&
|
||||
git config branch.ab.dummy AB &&
|
||||
git branch -C ab cd &&
|
||||
git reflog exists refs/heads/ab &&
|
||||
git reflog exists refs/heads/cd &&
|
||||
test $(git config branch.ab.dummy) = AB &&
|
||||
test $(git config branch.cd.dummy) = AB
|
||||
'
|
||||
|
||||
test_expect_success 'git branch -c correctly copies multiple config sections' '
|
||||
FOO=1 &&
|
||||
export FOO &&
|
||||
test_when_finished "git checkout master" &&
|
||||
git checkout -b source2 master &&
|
||||
|
||||
# Assert that a config file with multiple config sections has
|
||||
# those sections preserved...
|
||||
cat >expect <<-\EOF &&
|
||||
branch.source2.key1=value1
|
||||
branch.dest2.key1=value1
|
||||
more.gar.b=age
|
||||
branch.source2.key2=value2
|
||||
branch.dest2.key2=value2
|
||||
EOF
|
||||
cat >config.branch <<\EOF &&
|
||||
;; Note the lack of -\EOF above & mixed indenting here. This is
|
||||
;; intentional, we are also testing that the formatting of copied
|
||||
;; sections is preserved.
|
||||
|
||||
;; Comment for source2. Tabs
|
||||
[branch "source2"]
|
||||
;; Comment for the source2 value
|
||||
key1 = value1
|
||||
;; Comment for more.gar. Spaces
|
||||
[more "gar"]
|
||||
;; Comment for the more.gar value
|
||||
b = age
|
||||
;; Comment for source2, again. Mixed tabs/spaces.
|
||||
[branch "source2"]
|
||||
;; Comment for the source2 value, again
|
||||
key2 = value2
|
||||
EOF
|
||||
cat config.branch >>.git/config &&
|
||||
git branch -c source2 dest2 &&
|
||||
git config -f .git/config -l | grep -F -e source2 -e dest2 -e more.gar >actual &&
|
||||
test_cmp expect actual &&
|
||||
|
||||
# ...and that the comments and formatting for those sections
|
||||
# is also preserved.
|
||||
cat >expect <<\EOF &&
|
||||
;; Comment for source2. Tabs
|
||||
[branch "source2"]
|
||||
;; Comment for the source2 value
|
||||
key1 = value1
|
||||
;; Comment for more.gar. Spaces
|
||||
[branch "dest2"]
|
||||
;; Comment for the source2 value
|
||||
key1 = value1
|
||||
;; Comment for more.gar. Spaces
|
||||
[more "gar"]
|
||||
;; Comment for the more.gar value
|
||||
b = age
|
||||
;; Comment for source2, again. Mixed tabs/spaces.
|
||||
[branch "source2"]
|
||||
;; Comment for the source2 value, again
|
||||
key2 = value2
|
||||
[branch "dest2"]
|
||||
;; Comment for the source2 value, again
|
||||
key2 = value2
|
||||
EOF
|
||||
sed -n -e "/Comment for source2/,\$p" .git/config >actual &&
|
||||
test_cmp expect actual
|
||||
'
|
||||
|
||||
test_expect_success 'deleting a symref' '
|
||||
git branch target &&
|
||||
git symbolic-ref refs/heads/symref refs/heads/target &&
|
||||
|
|
Загрузка…
Ссылка в новой задаче