* [PATCH] branch: report kind of checkout when rejecting delete
@ 2026-07-18 4:39 René Scharfe
0 siblings, 0 replies; only message in thread
From: René Scharfe @ 2026-07-18 4:39 UTC (permalink / raw)
To: Git List; +Cc: stsp
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
^ permalink raw reply related [flat|nested] only message in thread
only message in thread, other threads:[~2026-07-18 4:39 UTC | newest]
Thread overview: (only message) (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-07-18 4:39 [PATCH] branch: report kind of checkout when rejecting delete René Scharfe
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox