Thread (10 messages) 10 messages, 4 authors, 11h ago

Re: [PATCH] branch: report kind of checkout when rejecting delete

From: Junio C Hamano <hidden>
Date: 2026-07-18 17:34:12

René Scharfe [off-list ref] writes:
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)
This reminds me of another recent discussion on rewriting a branch
that is checked out elsewhere, where the "git history" command
forgot to apply the same safety check:

https://lore.kernel.org/git/e7dbcede-4486-459c-aa64-e44690e01fe0@gmail.com/ (local)

We definitely need an easy-to-use API to determine consistently
which branches are in use, and to teach all commands that repoint
branch tips to use it to offer the same safety to users.  The
framework that this patch introduces might be a good starting point
for that effort.
quoted hunk ↗ jump to hunk
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,
+};
+
...
+/*
+ * 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);
OK.
quoted hunk ↗ jump to hunk
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);
Not "enum branch_checkout_kind" but "int"?
+				switch (kind) {
+				case BRANCH_CHECKOUT_KIND_CHECKOUT:
+					error(_("cannot delete branch '%s' "
+						"used by worktree at '%s'"),
+					      bname.buf, path);
+					break;
We may want to be more explicit and say "cannot delete
branch 'frotz' checked out in worktree at '/tmp/nitfol'"
instead.  Unless this is a catch-all entry for states that
are neither 'rebase', 'bisect', nor 'rebase-merges' but are
somehow otherwise in use, that is.
+				case BRANCH_CHECKOUT_KIND_UPDATE_REF:
+					error(_("cannot delete branch '%s' "
+						"used by worktree at '%s' "
+						"for update-ref"),
+					      bname.buf, path);
+					break;
I was quite lost when searching for cases where this 'update-ref'
state might be encountered, and I still lack confidence.  Can
we make the diagnostic message a bit friendlier to our users?

For instance, something like: 'You are rebasing a history with
merges in that other worktree, and the tip of this branch will
be updated when that process completes, so you cannot delete
it from here.'  (Naturally, I may have misidentified the exact
nature of the error, but this illustrates the level of detail and
user-facing clarity I hope to see.)
quoted hunk ↗ jump to hunk
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
 '
We distinguish four kinds in the code but we test only two of them?

Thanks.  I very much like the direction this is taking us.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help