From 7780bff8d161fe42d65954e026dcc621c50bd128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Scharfe?= Date: Sat, 25 Jul 2026 12:41:07 +0200 Subject: [PATCH] branch: report active bisect run when rejecting delete MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 Signed-off-by: RenĂ© Scharfe Signed-off-by: Junio C Hamano --- branch.c | 80 +++++++++++++++++++++++++++++++++-------------- branch.h | 6 ++++ builtin/branch.c | 7 +++++ t/t3200-branch.sh | 4 +-- 4 files changed, 72 insertions(+), 25 deletions(-) diff --git a/branch.c b/branch.c index 243db7d0fc..a9fc790818 100644 --- a/branch.c +++ b/branch.c @@ -385,6 +385,39 @@ int validate_branchname(const char *name, struct strbuf *ref) static int initialized_checked_out_branches; 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(¤t_checked_out_branches, refname, path_copy); +} + static void prepare_checked_out_branches(void) { int i = 0; @@ -397,7 +430,7 @@ static void prepare_checked_out_branches(void) worktrees = get_worktrees(); while (worktrees[i]) { - char *old, *wt_gitdir; + char *wt_gitdir; struct wt_status_state state = { 0 }; struct worktree *wt = worktrees[i++]; struct string_list update_refs = STRING_LIST_INIT_DUP; @@ -406,34 +439,25 @@ static void prepare_checked_out_branches(void) continue; if (wt->head_ref) { - old = strmap_put(¤t_checked_out_branches, - wt->head_ref, - xstrdup(wt->path)); - free(old); + register_checked_out_branch("", wt->head_ref, wt->path, + BRANCH_CHECKOUT_KIND_CHECKOUT); } if (wt_status_check_rebase(wt, &state) && (state.rebase_in_progress || state.rebase_interactive_in_progress) && state.branch) { - struct strbuf ref = STRBUF_INIT; - strbuf_addf(&ref, "refs/heads/%s", state.branch); - old = strmap_put(¤t_checked_out_branches, - ref.buf, - xstrdup(wt->path)); - free(old); - strbuf_release(&ref); + register_checked_out_branch("refs/heads/", state.branch, + wt->path, + BRANCH_CHECKOUT_KIND_REBASE); } wt_status_state_free_buffers(&state); if (wt_status_check_bisect(wt, &state) && state.bisecting_from) { - struct strbuf ref = STRBUF_INIT; - strbuf_addf(&ref, "refs/heads/%s", state.bisecting_from); - old = strmap_put(¤t_checked_out_branches, - ref.buf, - xstrdup(wt->path)); - free(old); - strbuf_release(&ref); + register_checked_out_branch("refs/heads/", + state.bisecting_from, + wt->path, + BRANCH_CHECKOUT_KIND_BISECT); } wt_status_state_free_buffers(&state); @@ -442,10 +466,9 @@ static void prepare_checked_out_branches(void) &update_refs)) { struct string_list_item *item; for_each_string_list_item(item, &update_refs) { - old = strmap_put(¤t_checked_out_branches, - item->string, - xstrdup(wt->path)); - free(old); + register_checked_out_branch("", item->string, + wt->path, + BRANCH_CHECKOUT_KIND_UPDATE_REF); } string_list_clear(&update_refs, 1); } @@ -462,6 +485,17 @@ const char *branch_checked_out(const char *refname) return strmap_get(¤t_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. * 'force' can be used when it is OK for the named branch already exists. diff --git a/branch.h b/branch.h index 3dc6e2a0ff..e9b1f7b37d 100644 --- a/branch.h +++ b/branch.h @@ -106,6 +106,12 @@ void create_branches_recursively(struct repository *r, const char *name, */ 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. * Return 1 if the named branch already exists; return 0 otherwise. diff --git a/builtin/branch.c b/builtin/branch.c index dede60d27b..29e4ec6c67 100644 --- a/builtin/branch.c +++ b/builtin/branch.c @@ -265,6 +265,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (kinds == FILTER_REFS_BRANCHES) { 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))) { error(_("cannot delete branch '%s' " "used by worktree at '%s'"), diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh index 1ecbafbee1..051434d9c6 100755 --- a/t/t3200-branch.sh +++ b/t/t3200-branch.sh @@ -930,7 +930,7 @@ test_expect_success 'deleting currently checked out branch fails' ' git worktree add -b my7 my7 && test_must_fail git -C my7 branch -d my7 && 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 && git worktree prune ' @@ -941,7 +941,7 @@ test_expect_success 'deleting in-use branch fails' ' git -C my7 bisect start HEAD HEAD~2 && test_must_fail git -C my7 branch -d my7 && 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 && git worktree prune '