Merge branch 'rj/branch-unborn-in-other-worktrees'
Error messages given when working on an unborn branch that is checked out in another worktree have been improved. * rj/branch-unborn-in-other-worktrees: branch: avoid unnecessary worktrees traversals branch: rename orphan branches in any worktree branch: description for orphan branch errors branch: use get_worktrees() in copy_or_rename_branch() branch: test for failures while renaming branches
This commit is contained in:
27
branch.c
27
branch.c
@ -840,30 +840,3 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree)
|
|||||||
|
|
||||||
free_worktrees(worktrees);
|
free_worktrees(worktrees);
|
||||||
}
|
}
|
||||||
|
|
||||||
int replace_each_worktree_head_symref(const char *oldref, const char *newref,
|
|
||||||
const char *logmsg)
|
|
||||||
{
|
|
||||||
int ret = 0;
|
|
||||||
struct worktree **worktrees = get_worktrees();
|
|
||||||
int i;
|
|
||||||
|
|
||||||
for (i = 0; worktrees[i]; i++) {
|
|
||||||
struct ref_store *refs;
|
|
||||||
|
|
||||||
if (worktrees[i]->is_detached)
|
|
||||||
continue;
|
|
||||||
if (!worktrees[i]->head_ref)
|
|
||||||
continue;
|
|
||||||
if (strcmp(oldref, worktrees[i]->head_ref))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
refs = get_worktree_ref_store(worktrees[i]);
|
|
||||||
if (refs_create_symref(refs, "HEAD", newref, logmsg))
|
|
||||||
ret = error(_("HEAD of working tree %s is not updated"),
|
|
||||||
worktrees[i]->path);
|
|
||||||
}
|
|
||||||
|
|
||||||
free_worktrees(worktrees);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|||||||
8
branch.h
8
branch.h
@ -155,12 +155,4 @@ int read_branch_desc(struct strbuf *, const char *branch_name);
|
|||||||
*/
|
*/
|
||||||
void die_if_checked_out(const char *branch, int ignore_current_worktree);
|
void die_if_checked_out(const char *branch, int ignore_current_worktree);
|
||||||
|
|
||||||
/*
|
|
||||||
* Update all per-worktree HEADs pointing at the old ref to point the new ref.
|
|
||||||
* This will be used when renaming a branch. Returns 0 if successful, non-zero
|
|
||||||
* otherwise.
|
|
||||||
*/
|
|
||||||
int replace_each_worktree_head_symref(const char *oldref, const char *newref,
|
|
||||||
const char *logmsg);
|
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@ -512,9 +512,9 @@ static void print_current_branch_name(void)
|
|||||||
die(_("HEAD (%s) points outside of refs/heads/"), refname);
|
die(_("HEAD (%s) points outside of refs/heads/"), refname);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void reject_rebase_or_bisect_branch(const char *target)
|
static void reject_rebase_or_bisect_branch(struct worktree **worktrees,
|
||||||
|
const char *target)
|
||||||
{
|
{
|
||||||
struct worktree **worktrees = get_worktrees();
|
|
||||||
int i;
|
int i;
|
||||||
|
|
||||||
for (i = 0; worktrees[i]; i++) {
|
for (i = 0; worktrees[i]; i++) {
|
||||||
@ -531,17 +531,50 @@ static void reject_rebase_or_bisect_branch(const char *target)
|
|||||||
die(_("Branch %s is being bisected at %s"),
|
die(_("Branch %s is being bisected at %s"),
|
||||||
target, wt->path);
|
target, wt->path);
|
||||||
}
|
}
|
||||||
|
|
||||||
free_worktrees(worktrees);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Update all per-worktree HEADs pointing at the old ref to point the new ref.
|
||||||
|
* This will be used when renaming a branch. Returns 0 if successful, non-zero
|
||||||
|
* otherwise.
|
||||||
|
*/
|
||||||
|
static int replace_each_worktree_head_symref(struct worktree **worktrees,
|
||||||
|
const char *oldref, const char *newref,
|
||||||
|
const char *logmsg)
|
||||||
|
{
|
||||||
|
int ret = 0;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for (i = 0; worktrees[i]; i++) {
|
||||||
|
struct ref_store *refs;
|
||||||
|
|
||||||
|
if (worktrees[i]->is_detached)
|
||||||
|
continue;
|
||||||
|
if (!worktrees[i]->head_ref)
|
||||||
|
continue;
|
||||||
|
if (strcmp(oldref, worktrees[i]->head_ref))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
refs = get_worktree_ref_store(worktrees[i]);
|
||||||
|
if (refs_create_symref(refs, "HEAD", newref, logmsg))
|
||||||
|
ret = error(_("HEAD of working tree %s is not updated"),
|
||||||
|
worktrees[i]->path);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define IS_HEAD 1
|
||||||
|
#define IS_ORPHAN 2
|
||||||
|
|
||||||
static void copy_or_rename_branch(const char *oldname, const char *newname, int copy, 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 oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
|
||||||
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
|
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
|
||||||
const char *interpreted_oldname = NULL;
|
const char *interpreted_oldname = NULL;
|
||||||
const char *interpreted_newname = NULL;
|
const char *interpreted_newname = NULL;
|
||||||
int recovery = 0;
|
int recovery = 0, oldref_usage = 0;
|
||||||
|
struct worktree **worktrees = get_worktrees();
|
||||||
|
|
||||||
if (strbuf_check_branch_ref(&oldref, oldname)) {
|
if (strbuf_check_branch_ref(&oldref, oldname)) {
|
||||||
/*
|
/*
|
||||||
@ -554,8 +587,19 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
|
|||||||
die(_("Invalid branch name: '%s'"), oldname);
|
die(_("Invalid branch name: '%s'"), oldname);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((copy || strcmp(head, oldname)) && !ref_exists(oldref.buf)) {
|
for (int i = 0; worktrees[i]; i++) {
|
||||||
if (copy && !strcmp(head, oldname))
|
struct worktree *wt = worktrees[i];
|
||||||
|
|
||||||
|
if (wt->head_ref && !strcmp(oldref.buf, wt->head_ref)) {
|
||||||
|
oldref_usage |= IS_HEAD;
|
||||||
|
if (is_null_oid(&wt->head_oid))
|
||||||
|
oldref_usage |= IS_ORPHAN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((copy || !(oldref_usage & IS_HEAD)) && !ref_exists(oldref.buf)) {
|
||||||
|
if (oldref_usage & IS_HEAD)
|
||||||
die(_("No commit on branch '%s' yet."), oldname);
|
die(_("No commit on branch '%s' yet."), oldname);
|
||||||
else
|
else
|
||||||
die(_("No branch named '%s'."), oldname);
|
die(_("No branch named '%s'."), oldname);
|
||||||
@ -570,7 +614,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
|
|||||||
else
|
else
|
||||||
validate_new_branchname(newname, &newref, force);
|
validate_new_branchname(newname, &newref, force);
|
||||||
|
|
||||||
reject_rebase_or_bisect_branch(oldref.buf);
|
reject_rebase_or_bisect_branch(worktrees, oldref.buf);
|
||||||
|
|
||||||
if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
|
if (!skip_prefix(oldref.buf, "refs/heads/", &interpreted_oldname) ||
|
||||||
!skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
|
!skip_prefix(newref.buf, "refs/heads/", &interpreted_newname)) {
|
||||||
@ -584,8 +628,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
|
|||||||
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
|
strbuf_addf(&logmsg, "Branch: renamed %s to %s",
|
||||||
oldref.buf, newref.buf);
|
oldref.buf, newref.buf);
|
||||||
|
|
||||||
if (!copy &&
|
if (!copy && !(oldref_usage & IS_ORPHAN) &&
|
||||||
(!head || strcmp(oldname, head) || !is_null_oid(&head_oid)) &&
|
|
||||||
rename_ref(oldref.buf, newref.buf, logmsg.buf))
|
rename_ref(oldref.buf, newref.buf, logmsg.buf))
|
||||||
die(_("Branch rename failed"));
|
die(_("Branch rename failed"));
|
||||||
if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
|
if (copy && copy_existing_ref(oldref.buf, newref.buf, logmsg.buf))
|
||||||
@ -600,8 +643,9 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
|
|||||||
interpreted_oldname);
|
interpreted_oldname);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!copy &&
|
if (!copy && (oldref_usage & IS_HEAD) &&
|
||||||
replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
|
replace_each_worktree_head_symref(worktrees, oldref.buf, newref.buf,
|
||||||
|
logmsg.buf))
|
||||||
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
|
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
|
||||||
|
|
||||||
strbuf_release(&logmsg);
|
strbuf_release(&logmsg);
|
||||||
@ -616,6 +660,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
|
|||||||
strbuf_release(&newref);
|
strbuf_release(&newref);
|
||||||
strbuf_release(&oldsection);
|
strbuf_release(&oldsection);
|
||||||
strbuf_release(&newsection);
|
strbuf_release(&newsection);
|
||||||
|
free_worktrees(worktrees);
|
||||||
}
|
}
|
||||||
|
|
||||||
static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
|
static GIT_PATH_FUNC(edit_description, "EDIT_DESCRIPTION")
|
||||||
@ -834,7 +879,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
|||||||
|
|
||||||
strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
|
strbuf_addf(&branch_ref, "refs/heads/%s", branch_name);
|
||||||
if (!ref_exists(branch_ref.buf))
|
if (!ref_exists(branch_ref.buf))
|
||||||
error((!argc || !strcmp(head, branch_name))
|
error((!argc || branch_checked_out(branch_ref.buf))
|
||||||
? _("No commit on branch '%s' yet.")
|
? _("No commit on branch '%s' yet.")
|
||||||
: _("No branch named '%s'."),
|
: _("No branch named '%s'."),
|
||||||
branch_name);
|
branch_name);
|
||||||
@ -879,7 +924,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!ref_exists(branch->refname)) {
|
if (!ref_exists(branch->refname)) {
|
||||||
if (!argc || !strcmp(head, branch->name))
|
if (!argc || branch_checked_out(branch->refname))
|
||||||
die(_("No commit on branch '%s' yet."), branch->name);
|
die(_("No commit on branch '%s' yet."), branch->name);
|
||||||
die(_("branch '%s' does not exist"), branch->name);
|
die(_("branch '%s' does not exist"), branch->name);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -239,6 +239,21 @@ test_expect_success 'git branch -M baz bam should succeed when baz is checked ou
|
|||||||
git worktree prune
|
git worktree prune
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git branch -M fails if updating any linked working tree fails' '
|
||||||
|
git worktree add -b baz bazdir1 &&
|
||||||
|
git worktree add -f bazdir2 baz &&
|
||||||
|
touch .git/worktrees/bazdir1/HEAD.lock &&
|
||||||
|
test_must_fail git branch -M baz bam &&
|
||||||
|
test $(git -C bazdir2 rev-parse --abbrev-ref HEAD) = bam &&
|
||||||
|
git branch -M bam baz &&
|
||||||
|
rm .git/worktrees/bazdir1/HEAD.lock &&
|
||||||
|
touch .git/worktrees/bazdir2/HEAD.lock &&
|
||||||
|
test_must_fail git branch -M baz bam &&
|
||||||
|
test $(git -C bazdir1 rev-parse --abbrev-ref HEAD) = bam &&
|
||||||
|
rm -rf bazdir1 bazdir2 &&
|
||||||
|
git worktree prune
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' '
|
test_expect_success 'git branch -M baz bam should succeed within a worktree in which baz is checked out' '
|
||||||
git checkout -b baz &&
|
git checkout -b baz &&
|
||||||
git worktree add -f bazdir baz &&
|
git worktree add -f bazdir baz &&
|
||||||
@ -283,6 +298,20 @@ test_expect_success 'git branch -M and -C fail on detached HEAD' '
|
|||||||
test_cmp expect err
|
test_cmp expect err
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'git branch -m should work with orphan branches' '
|
||||||
|
test_when_finished git checkout - &&
|
||||||
|
test_when_finished git worktree remove -f wt &&
|
||||||
|
git worktree add wt --detach &&
|
||||||
|
# rename orphan in another worktreee
|
||||||
|
git -C wt checkout --orphan orphan-foo-wt &&
|
||||||
|
git branch -m orphan-foo-wt orphan-bar-wt &&
|
||||||
|
test orphan-bar-wt=$(git -C orphan-worktree branch --show-current) &&
|
||||||
|
# rename orphan in the current worktree
|
||||||
|
git checkout --orphan orphan-foo &&
|
||||||
|
git branch -m orphan-foo orphan-bar &&
|
||||||
|
test orphan-bar=$(git branch --show-current)
|
||||||
|
'
|
||||||
|
|
||||||
test_expect_success 'git branch -d on orphan HEAD (merged)' '
|
test_expect_success 'git branch -d on orphan HEAD (merged)' '
|
||||||
test_when_finished git checkout main &&
|
test_when_finished git checkout main &&
|
||||||
git checkout --orphan orphan &&
|
git checkout --orphan orphan &&
|
||||||
|
|||||||
@ -221,4 +221,22 @@ test_expect_success 'fatal descriptions on non-existent branch' '
|
|||||||
test_cmp expect actual
|
test_cmp expect actual
|
||||||
'
|
'
|
||||||
|
|
||||||
|
test_expect_success 'error descriptions on orphan branch' '
|
||||||
|
test_when_finished git worktree remove -f wt &&
|
||||||
|
git worktree add wt --detach &&
|
||||||
|
git -C wt checkout --orphan orphan-branch &&
|
||||||
|
test_branch_op_in_wt() {
|
||||||
|
test_orphan_error() {
|
||||||
|
test_must_fail git $* 2>actual &&
|
||||||
|
test_i18ngrep "No commit on branch .orphan-branch. yet.$" actual
|
||||||
|
} &&
|
||||||
|
test_orphan_error -C wt branch $1 $2 && # implicit branch
|
||||||
|
test_orphan_error -C wt branch $1 orphan-branch $2 && # explicit branch
|
||||||
|
test_orphan_error branch $1 orphan-branch $2 # different worktree
|
||||||
|
} &&
|
||||||
|
test_branch_op_in_wt --edit-description &&
|
||||||
|
test_branch_op_in_wt --set-upstream-to=ne &&
|
||||||
|
test_branch_op_in_wt -c new-branch
|
||||||
|
'
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|||||||
Reference in New Issue
Block a user