Git development
 help / color / mirror / Atom feed
From: "René Scharfe" <l.s.r@web.de>
To: Git List <git@vger.kernel.org>
Cc: stsp <stsp2@yandex.ru>, Phillip Wood <phillip.wood@dunelm.org.uk>,
	Junio C Hamano <gitster@pobox.com>
Subject: [PATCH v2] branch: report active bisect run when rejecting delete
Date: Sat, 25 Jul 2026 12:41:07 +0200	[thread overview]
Message-ID: <590382fb-731b-4e14-911e-ff68356d1082@web.de> (raw)
In-Reply-To: <9865fc6b-e3fe-4614-9ffe-71af776e1796@web.de>

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>
---
Changes since v2:
- Only report bisect runs as specific rejection reason for now.
- Leave rebasing unaddressed because I don't see how to formulate
  clear and readable messages for those scenarios, yet.
- Collect all checkout reasons of all branches in a simple array for
  easy use, e.g. to eventually address rebases or for git status.
- Rebased onto the landed test_grep conversion.

 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(&current_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(&current_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(&current_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(&current_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(&current_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(&current_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
 '
-- 
2.55.0

  parent reply	other threads:[~2026-07-25 10:41 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-07-18  4:39 [PATCH] branch: report kind of checkout when rejecting delete René Scharfe
2026-07-18 17:34 ` Junio C Hamano
2026-07-18 19:07   ` René Scharfe
2026-07-18 22:09     ` Junio C Hamano
2026-07-19  5:55       ` René Scharfe
2026-07-19  9:50       ` Phillip Wood
2026-07-25 10:41 ` René Scharfe [this message]
2026-07-26 15:36   ` [PATCH v2] branch: report active bisect run " Junio C Hamano
2026-07-28 13:36     ` Toon Claes

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=590382fb-731b-4e14-911e-ff68356d1082@web.de \
    --to=l.s.r@web.de \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=phillip.wood@dunelm.org.uk \
    --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 a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox