[PATCH] branch: report kind of checkout when rejecting delete
From: René Scharfe <hidden>
Date: 2026-07-18 04:39:21
Subsystem:
the rest · Maintainer:
Linus Torvalds
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 with internal checkouts, e.g. if one tries to delete a branch associated with an active bisect run. Mention the kind of internal checkout, if any, to spare the user from remembering that they might have forgotten a bisect or rebase. To do that, register the checkout reason in a strintmap alongside the existing strmap that stores the worktree path. Suggested-by: stsp <redacted> Signed-off-by: René Scharfe <redacted> --- Original message: https://lore.kernel.org/git/cae34516-5437-49d3-8d39-16f4059a81a8@yandex.ru/ (local) branch.c | 48 ++++++++++++++++++++++++++--------------------- branch.h | 15 +++++++++++++++ builtin/branch.c | 31 +++++++++++++++++++++++++++--- t/t3200-branch.sh | 4 ++-- 4 files changed, 72 insertions(+), 26 deletions(-)
diff --git a/branch.c b/branch.c
index 243db7d0fc..aaa54f1b62 100644
--- a/branch.c
+++ b/branch.c@@ -384,6 +384,16 @@ int validate_branchname(const char *name, struct strbuf *ref) static int initialized_checked_out_branches; static struct strmap current_checked_out_branches = STRMAP_INIT; +static struct strintmap current_checked_out_branch_kinds = STRINTMAP_INIT; + +static void register_checked_out_branch(const char *refname, const char *path, + enum branch_checkout_kind kind) +{ + char *old = strmap_put(¤t_checked_out_branches, refname, + xstrdup(path)); + free(old); + strintmap_set(¤t_checked_out_branch_kinds, refname, kind); +} static void prepare_checked_out_branches(void) {
@@ -397,7 +407,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;
@@ -405,22 +415,17 @@ static void prepare_checked_out_branches(void) if (wt->is_bare) continue; - if (wt->head_ref) { - old = strmap_put(¤t_checked_out_branches, - wt->head_ref, - xstrdup(wt->path)); - free(old); - } + if (wt->head_ref) + 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); + register_checked_out_branch(ref.buf, wt->path, + BRANCH_CHECKOUT_KIND_REBASE); strbuf_release(&ref); } wt_status_state_free_buffers(&state);
@@ -429,10 +434,8 @@ static void prepare_checked_out_branches(void) 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); + register_checked_out_branch(ref.buf, wt->path, + BRANCH_CHECKOUT_KIND_BISECT); strbuf_release(&ref); } wt_status_state_free_buffers(&state);
@@ -441,12 +444,9 @@ static void prepare_checked_out_branches(void) if (!sequencer_get_update_refs_state(wt_gitdir, &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); - } + for_each_string_list_item(item, &update_refs) + register_checked_out_branch(item->string, wt->path, + BRANCH_CHECKOUT_KIND_UPDATE_REF); string_list_clear(&update_refs, 1); }
@@ -462,6 +462,12 @@ const char *branch_checked_out(const char *refname) return strmap_get(¤t_checked_out_branches, refname); } +enum branch_checkout_kind branch_checkout_kind(const char *refname) +{ + prepare_checked_out_branches(); + return strintmap_get(¤t_checked_out_branch_kinds, refname); +} + /* * 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..d1073fe1cd 100644
--- a/branch.h
+++ b/branch.h@@ -15,6 +15,14 @@ enum branch_track { BRANCH_TRACK_SIMPLE, }; +enum branch_checkout_kind { + BRANCH_CHECKOUT_KIND_UNSPECIFIED = 0, + BRANCH_CHECKOUT_KIND_CHECKOUT, + BRANCH_CHECKOUT_KIND_REBASE, + BRANCH_CHECKOUT_KIND_BISECT, + BRANCH_CHECKOUT_KIND_UPDATE_REF, +}; + /* Functions for acting on the information about branches. */ /**
@@ -106,6 +114,13 @@ 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 checked out in a worktree, + * then return the kind of checkout, i.e. whether it was done by an + * actual checkout or a rebase etc. + */ +enum branch_checkout_kind branch_checkout_kind(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..3223347129 100644
--- a/builtin/branch.c
+++ b/builtin/branch.c@@ -266,9 +266,34 @@ static int delete_branches(int argc, const char **argv, int force, int kinds, if (kinds == FILTER_REFS_BRANCHES) { const char *path; if ((path = branch_checked_out(name))) { - error(_("cannot delete branch '%s' " - "used by worktree at '%s'"), - bname.buf, path); + int kind = branch_checkout_kind(name); + switch (kind) { + case BRANCH_CHECKOUT_KIND_CHECKOUT: + error(_("cannot delete branch '%s' " + "used by worktree at '%s'"), + bname.buf, path); + break; + case BRANCH_CHECKOUT_KIND_REBASE: + error(_("cannot delete branch '%s' " + "used by worktree at '%s' " + "for rebase"), + bname.buf, path); + break; + case BRANCH_CHECKOUT_KIND_BISECT: + error(_("cannot delete branch '%s' " + "used by worktree at '%s' " + "for bisect"), + bname.buf, path); + break; + case BRANCH_CHECKOUT_KIND_UPDATE_REF: + error(_("cannot delete branch '%s' " + "used by worktree at '%s' " + "for update-ref"), + bname.buf, path); + break; + default: + BUG("invalid checkout kind %d", kind); + } ret = 1; continue; }
diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index e2682a83a0..e5df493b66 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 && - 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 && - 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 '
--
2.55.0