branch: report active bisect run when rejecting delete

git branch refuses to delete branches that are currently checked out with a message like this: "error: cannot delete branch 'foo' used by worktree at '/path/of/worktree'". This can be confusing if it's an internal checkout for git bisect. Report a more specific error in that case to help users that might have forgotten their bisect run. Suggested-by: stsp <stsp2@yandex.ru> Signed-off-by: René Scharfe <l.s.r@web.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

René Scharfe committed Jul 25, 2026 at 12:41 UTC 7780bff8d161fe42d65954e026dcc621c50bd128
4 files changed +72 -25
branch.c
+57 -23
@@ -385,6 +385,39 @@ int validate_branchname(const char *name, struct strbuf *ref)
385 static int initialized_checked_out_branches;
386 static struct strmap current_checked_out_branches = STRMAP_INIT;
387
388 +enum branch_checkout_kind {
389 + BRANCH_CHECKOUT_KIND_CHECKOUT,
390 + BRANCH_CHECKOUT_KIND_REBASE,
391 + BRANCH_CHECKOUT_KIND_BISECT,
392 + BRANCH_CHECKOUT_KIND_UPDATE_REF,
393 +};
394 +
395 +struct checked_out_branch {
396 + char *refname;
397 + char *path;
398 + enum branch_checkout_kind kind;
399 +};
400 +
401 +static struct checked_out_branch *checked_out_branches;
402 +static size_t checked_out_branches_alloc, checked_out_branches_nr;
403 +
404 +static void register_checked_out_branch(const char *prefix, const char *name,
405 + const char *path,
406 + enum branch_checkout_kind kind)
407 +{
408 + char *refname = xstrfmt("%s%s", prefix, name);
409 + char *path_copy = xstrdup(path);
410 +
411 + ALLOC_GROW(checked_out_branches, checked_out_branches_nr + 1,
412 + checked_out_branches_alloc);
413 + checked_out_branches[checked_out_branches_nr].refname = refname;
414 + checked_out_branches[checked_out_branches_nr].path = path_copy;
415 + checked_out_branches[checked_out_branches_nr].kind = kind;
416 + checked_out_branches_nr++;
417 +
418 + strmap_put(&current_checked_out_branches, refname, path_copy);
419 +}
420 +
421 static void prepare_checked_out_branches(void)
422 {
423 int i = 0;
@@ -397,7 +430,7 @@ static void prepare_checked_out_branches(void)
430 worktrees = get_worktrees();
431
432 while (worktrees[i]) {
400 - char *old, *wt_gitdir;
433 + char *wt_gitdir;
434 struct wt_status_state state = { 0 };
435 struct worktree *wt = worktrees[i++];
436 struct string_list update_refs = STRING_LIST_INIT_DUP;
@@ -406,34 +439,25 @@ static void prepare_checked_out_branches(void)
439 continue;
440
441 if (wt->head_ref) {
409 - old = strmap_put(&current_checked_out_branches,
410 - wt->head_ref,
411 - xstrdup(wt->path));
412 - free(old);
442 + register_checked_out_branch("", wt->head_ref, wt->path,
443 + BRANCH_CHECKOUT_KIND_CHECKOUT);
444 }
445
446 if (wt_status_check_rebase(wt, &state) &&
447 (state.rebase_in_progress || state.rebase_interactive_in_progress) &&
448 state.branch) {
418 - struct strbuf ref = STRBUF_INIT;
419 - strbuf_addf(&ref, "refs/heads/%s", state.branch);
420 - old = strmap_put(&current_checked_out_branches,
421 - ref.buf,
422 - xstrdup(wt->path));
423 - free(old);
424 - strbuf_release(&ref);
449 + register_checked_out_branch("refs/heads/", state.branch,
450 + wt->path,
451 + BRANCH_CHECKOUT_KIND_REBASE);
452 }
453 wt_status_state_free_buffers(&state);
454
455 if (wt_status_check_bisect(wt, &state) &&
456 state.bisecting_from) {
430 - struct strbuf ref = STRBUF_INIT;
431 - strbuf_addf(&ref, "refs/heads/%s", state.bisecting_from);
432 - old = strmap_put(&current_checked_out_branches,
433 - ref.buf,
434 - xstrdup(wt->path));
435 - free(old);
436 - strbuf_release(&ref);
457 + register_checked_out_branch("refs/heads/",
458 + state.bisecting_from,
459 + wt->path,
460 + BRANCH_CHECKOUT_KIND_BISECT);
461 }
462 wt_status_state_free_buffers(&state);
463
@@ -442,10 +466,9 @@ static void prepare_checked_out_branches(void)
466 &update_refs)) {
467 struct string_list_item *item;
468 for_each_string_list_item(item, &update_refs) {
445 - old = strmap_put(&current_checked_out_branches,
446 - item->string,
447 - xstrdup(wt->path));
448 - free(old);
469 + register_checked_out_branch("", item->string,
470 + wt->path,
471 + BRANCH_CHECKOUT_KIND_UPDATE_REF);
472 }
473 string_list_clear(&update_refs, 1);
474 }
@@ -462,6 +485,17 @@ const char *branch_checked_out(const char *refname)
485 return strmap_get(&current_checked_out_branches, refname);
486 }
487
488 +const char *branch_bisecting(const char *refname)
489 +{
490 + prepare_checked_out_branches();
491 + for (size_t i = 0; i < checked_out_branches_nr; i++) {
492 + if (!strcmp(refname, checked_out_branches[i].refname) &&
493 + checked_out_branches[i].kind == BRANCH_CHECKOUT_KIND_BISECT)
494 + return checked_out_branches[i].path;
495 + }
496 + return NULL;
497 +}
498 +
499 /*
500 * Check if a branch 'name' can be created as a new branch; die otherwise.
501 * 'force' can be used when it is OK for the named branch already exists.
branch.h
+6
@@ -106,6 +106,12 @@ void create_branches_recursively(struct repository *r, const char *name,
106 */
107 const char *branch_checked_out(const char *refname);
108
109 +/*
110 + * If the branch at 'refname' is currently used for bisecting in a
111 + * worktree, then return the path to that worktree.
112 + */
113 +const char *branch_bisecting(const char *refname);
114 +
115 /*
116 * Check if 'name' can be a valid name for a branch; die otherwise.
117 * Return 1 if the named branch already exists; return 0 otherwise.
builtin/branch.c
+7
@@ -265,6 +265,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
265
266 if (kinds == FILTER_REFS_BRANCHES) {
267 const char *path;
268 + if ((path = branch_bisecting(name))) {
269 + error(_("cannot delete branch '%s' "
270 + "used by worktree at '%s' for bisect"),
271 + bname.buf, path);
272 + ret = 1;
273 + continue;
274 + }
275 if ((path = branch_checked_out(name))) {
276 error(_("cannot delete branch '%s' "
277 "used by worktree at '%s'"),
t/t3200-branch.sh
+2 -2
@@ -930,7 +930,7 @@ test_expect_success 'deleting currently checked out branch fails' '
930 git worktree add -b my7 my7 &&
931 test_must_fail git -C my7 branch -d my7 &&
932 test_must_fail git branch -d my7 2>actual &&
933 - test_grep "^error: cannot delete branch .my7. used by worktree at " actual &&
933 + test_grep "^error: cannot delete branch '"'"'my7'"'"' used by worktree at '"'.*'\$"'" actual &&
934 rm -r my7 &&
935 git worktree prune
936 '
@@ -941,7 +941,7 @@ test_expect_success 'deleting in-use branch fails' '
941 git -C my7 bisect start HEAD HEAD~2 &&
942 test_must_fail git -C my7 branch -d my7 &&
943 test_must_fail git branch -d my7 2>actual &&
944 - test_grep "^error: cannot delete branch .my7. used by worktree at " actual &&
944 + test_grep "^error: cannot delete branch '"'"'my7'"'"' used by worktree at '"'.*' for bisect\$"'" actual &&
945 rm -r my7 &&
946 git worktree prune
947 '