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:
Junio C Hamano 2023-05-15 13:59:02 -07:00
Родитель 5bc069e383 3521c63213
Коммит d3f2e4ab13
5 изменённых файлов: 106 добавлений и 49 удалений

Просмотреть файл

@ -840,30 +840,3 @@ void die_if_checked_out(const char *branch, int ignore_current_worktree)
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;
}

Просмотреть файл

@ -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);
/*
* 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

Просмотреть файл

@ -512,9 +512,9 @@ static void print_current_branch_name(void)
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;
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"),
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)
{
struct strbuf oldref = STRBUF_INIT, newref = STRBUF_INIT, logmsg = STRBUF_INIT;
struct strbuf oldsection = STRBUF_INIT, newsection = STRBUF_INIT;
const char *interpreted_oldname = 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)) {
/*
@ -554,8 +587,19 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
die(_("Invalid branch name: '%s'"), oldname);
}
if ((copy || strcmp(head, oldname)) && !ref_exists(oldref.buf)) {
if (copy && !strcmp(head, oldname))
for (int i = 0; worktrees[i]; i++) {
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);
else
die(_("No branch named '%s'."), oldname);
@ -570,7 +614,7 @@ static void copy_or_rename_branch(const char *oldname, const char *newname, int
else
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) ||
!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",
oldref.buf, newref.buf);
if (!copy &&
(!head || strcmp(oldname, head) || !is_null_oid(&head_oid)) &&
if (!copy && !(oldref_usage & IS_ORPHAN) &&
rename_ref(oldref.buf, newref.buf, logmsg.buf))
die(_("Branch rename failed"));
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);
}
if (!copy &&
replace_each_worktree_head_symref(oldref.buf, newref.buf, logmsg.buf))
if (!copy && (oldref_usage & IS_HEAD) &&
replace_each_worktree_head_symref(worktrees, oldref.buf, newref.buf,
logmsg.buf))
die(_("Branch renamed to %s, but HEAD is not updated!"), newname);
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(&oldsection);
strbuf_release(&newsection);
free_worktrees(worktrees);
}
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);
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 branch named '%s'."),
branch_name);
@ -879,7 +924,7 @@ int cmd_branch(int argc, const char **argv, const char *prefix)
}
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(_("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
'
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' '
git checkout -b 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_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_when_finished git checkout main &&
git checkout --orphan orphan &&

Просмотреть файл

@ -221,4 +221,22 @@ test_expect_success 'fatal descriptions on non-existent branch' '
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