From: "René Scharfe" <l.s.r@web.de>
To: Git List <git@vger.kernel.org>
Cc: stsp <stsp2@yandex.ru>
Subject: [PATCH] branch: report kind of checkout when rejecting delete
Date: Sat, 18 Jul 2026 06:39:18 +0200 [thread overview]
Message-ID: <9865fc6b-e3fe-4614-9ffe-71af776e1796@web.de> (raw)
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 <stsp2@yandex.ru>
Signed-off-by: René Scharfe <l.s.r@web.de>
---
Original message:
https://lore.kernel.org/git/cae34516-5437-49d3-8d39-16f4059a81a8@yandex.ru/
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
next reply other threads:[~2026-07-18 4:39 UTC|newest]
Thread overview: 4+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-18 4:39 René Scharfe [this message]
2026-07-18 17:34 ` [PATCH] branch: report kind of checkout when rejecting delete Junio C Hamano
2026-07-18 19:07 ` René Scharfe
2026-07-18 22:09 ` Junio C Hamano
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=9865fc6b-e3fe-4614-9ffe-71af776e1796@web.de \
--to=l.s.r@web.de \
--cc=git@vger.kernel.org \
--cc=stsp2@yandex.ru \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.