Merge branch 'rs/branch-delete-bisect-warning'

'git branch -d' has been taught to report when a branch cannot be
deleted because it is being used in an active bisect run.

* rs/branch-delete-bisect-warning:
  branch: report active bisect run when rejecting delete
main
Junio C Hamano 2026-08-05 11:10:51 -07:00
commit 67b1d2fa43
4 changed files with 72 additions and 25 deletions

View File

@ -385,6 +385,39 @@ int validate_branchname(const char *name, struct strbuf *ref)
static int initialized_checked_out_branches; static int initialized_checked_out_branches;
static struct strmap current_checked_out_branches = STRMAP_INIT; static struct strmap current_checked_out_branches = STRMAP_INIT;


enum branch_checkout_kind {
BRANCH_CHECKOUT_KIND_CHECKOUT,
BRANCH_CHECKOUT_KIND_REBASE,
BRANCH_CHECKOUT_KIND_BISECT,
BRANCH_CHECKOUT_KIND_UPDATE_REF,
};

struct checked_out_branch {
char *refname;
char *path;
enum branch_checkout_kind kind;
};

static struct checked_out_branch *checked_out_branches;
static size_t checked_out_branches_alloc, checked_out_branches_nr;

static void register_checked_out_branch(const char *prefix, const char *name,
const char *path,
enum branch_checkout_kind kind)
{
char *refname = xstrfmt("%s%s", prefix, name);
char *path_copy = xstrdup(path);

ALLOC_GROW(checked_out_branches, checked_out_branches_nr + 1,
checked_out_branches_alloc);
checked_out_branches[checked_out_branches_nr].refname = refname;
checked_out_branches[checked_out_branches_nr].path = path_copy;
checked_out_branches[checked_out_branches_nr].kind = kind;
checked_out_branches_nr++;

strmap_put(&current_checked_out_branches, refname, path_copy);
}

static void prepare_checked_out_branches(void) static void prepare_checked_out_branches(void)
{ {
int i = 0; int i = 0;
@ -397,7 +430,7 @@ static void prepare_checked_out_branches(void)
worktrees = get_worktrees(the_repository); worktrees = get_worktrees(the_repository);


while (worktrees[i]) { while (worktrees[i]) {
char *old, *wt_gitdir; char *wt_gitdir;
struct wt_status_state state = { 0 }; struct wt_status_state state = { 0 };
struct worktree *wt = worktrees[i++]; struct worktree *wt = worktrees[i++];
struct string_list update_refs = STRING_LIST_INIT_DUP; struct string_list update_refs = STRING_LIST_INIT_DUP;
@ -406,34 +439,25 @@ static void prepare_checked_out_branches(void)
continue; continue;


if (wt->head_ref) { if (wt->head_ref) {
old = strmap_put(&current_checked_out_branches, register_checked_out_branch("", wt->head_ref, wt->path,
wt->head_ref, BRANCH_CHECKOUT_KIND_CHECKOUT);
xstrdup(wt->path));
free(old);
} }


if (wt_status_check_rebase(wt, &state) && if (wt_status_check_rebase(wt, &state) &&
(state.rebase_in_progress || state.rebase_interactive_in_progress) && (state.rebase_in_progress || state.rebase_interactive_in_progress) &&
state.branch) { state.branch) {
struct strbuf ref = STRBUF_INIT; register_checked_out_branch("refs/heads/", state.branch,
strbuf_addf(&ref, "refs/heads/%s", state.branch); wt->path,
old = strmap_put(&current_checked_out_branches, BRANCH_CHECKOUT_KIND_REBASE);
ref.buf,
xstrdup(wt->path));
free(old);
strbuf_release(&ref);
} }
wt_status_state_free_buffers(&state); wt_status_state_free_buffers(&state);


if (wt_status_check_bisect(wt, &state) && if (wt_status_check_bisect(wt, &state) &&
state.bisecting_from) { state.bisecting_from) {
struct strbuf ref = STRBUF_INIT; register_checked_out_branch("refs/heads/",
strbuf_addf(&ref, "refs/heads/%s", state.bisecting_from); state.bisecting_from,
old = strmap_put(&current_checked_out_branches, wt->path,
ref.buf, BRANCH_CHECKOUT_KIND_BISECT);
xstrdup(wt->path));
free(old);
strbuf_release(&ref);
} }
wt_status_state_free_buffers(&state); wt_status_state_free_buffers(&state);


@ -442,10 +466,9 @@ static void prepare_checked_out_branches(void)
&update_refs)) { &update_refs)) {
struct string_list_item *item; struct string_list_item *item;
for_each_string_list_item(item, &update_refs) { for_each_string_list_item(item, &update_refs) {
old = strmap_put(&current_checked_out_branches, register_checked_out_branch("", item->string,
item->string, wt->path,
xstrdup(wt->path)); BRANCH_CHECKOUT_KIND_UPDATE_REF);
free(old);
} }
string_list_clear(&update_refs, 1); string_list_clear(&update_refs, 1);
} }
@ -462,6 +485,17 @@ const char *branch_checked_out(const char *refname)
return strmap_get(&current_checked_out_branches, refname); return strmap_get(&current_checked_out_branches, refname);
} }


const char *branch_bisecting(const char *refname)
{
prepare_checked_out_branches();
for (size_t i = 0; i < checked_out_branches_nr; i++) {
if (!strcmp(refname, checked_out_branches[i].refname) &&
checked_out_branches[i].kind == BRANCH_CHECKOUT_KIND_BISECT)
return checked_out_branches[i].path;
}
return NULL;
}

/* /*
* Check if a branch 'name' can be created as a new branch; die otherwise. * Check if a branch 'name' can be created as a new branch; die otherwise.
* 'force' can be used when it is OK for the named branch already exists. * 'force' can be used when it is OK for the named branch already exists.

View File

@ -106,6 +106,12 @@ void create_branches_recursively(struct repository *r, const char *name,
*/ */
const char *branch_checked_out(const char *refname); const char *branch_checked_out(const char *refname);


/*
* If the branch at 'refname' is currently used for bisecting in a
* worktree, then return the path to that worktree.
*/
const char *branch_bisecting(const char *refname);

/* /*
* Check if 'name' can be a valid name for a branch; die otherwise. * Check if 'name' can be a valid name for a branch; die otherwise.
* Return 1 if the named branch already exists; return 0 otherwise. * Return 1 if the named branch already exists; return 0 otherwise.

View File

@ -266,6 +266,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,


if (kinds == FILTER_REFS_BRANCHES) { if (kinds == FILTER_REFS_BRANCHES) {
const char *path; const char *path;
if ((path = branch_bisecting(name))) {
error(_("cannot delete branch '%s' "
"used by worktree at '%s' for bisect"),
bname.buf, path);
ret = 1;
continue;
}
if ((path = branch_checked_out(name))) { if ((path = branch_checked_out(name))) {
error(_("cannot delete branch '%s' " error(_("cannot delete branch '%s' "
"used by worktree at '%s'"), "used by worktree at '%s'"),

View File

@ -930,7 +930,7 @@ test_expect_success 'deleting currently checked out branch fails' '
git worktree add -b my7 my7 && git worktree add -b my7 my7 &&
test_must_fail git -C my7 branch -d my7 && test_must_fail git -C my7 branch -d my7 &&
test_must_fail git branch -d my7 2>actual && test_must_fail git branch -d my7 2>actual &&
test_grep "^error: cannot delete branch .my7. used by worktree at " actual && test_grep "^error: cannot delete branch '"'"'my7'"'"' used by worktree at '"'.*'\$"'" actual &&
rm -r my7 && rm -r my7 &&
git worktree prune git worktree prune
' '
@ -941,7 +941,7 @@ test_expect_success 'deleting in-use branch fails' '
git -C my7 bisect start HEAD HEAD~2 && git -C my7 bisect start HEAD HEAD~2 &&
test_must_fail git -C my7 branch -d my7 && test_must_fail git -C my7 branch -d my7 &&
test_must_fail git branch -d my7 2>actual && test_must_fail git branch -d my7 2>actual &&
test_grep "^error: cannot delete branch .my7. used by worktree at " actual && test_grep "^error: cannot delete branch '"'"'my7'"'"' used by worktree at '"'.*' for bisect\$"'" actual &&
rm -r my7 && rm -r my7 &&
git worktree prune git worktree prune
' '