branch: report active bisect run when rejecting delete
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> Signed-off-by: Junio C Hamano <gitster@pobox.com>
René Scharfe committed
Jul 25, 2026 at 12:41 UTC
7780bff8d161fe42d65954e026dcc621c50bd128
4 files changed
+72
-25
branch.c
+57
-23
@@ -385,6 +385,39 @@ int validate_branchname(const char *name, struct strbuf *ref)
385
static int initialized_checked_out_branches;
386
static struct strmap current_checked_out_branches = STRMAP_INIT;
387
388
+enum branch_checkout_kind {
389
+ BRANCH_CHECKOUT_KIND_CHECKOUT,
390
+ BRANCH_CHECKOUT_KIND_REBASE,
391
+ BRANCH_CHECKOUT_KIND_BISECT,
392
+ BRANCH_CHECKOUT_KIND_UPDATE_REF,
393
+};
394
+
395
+struct checked_out_branch {
396
+ char *refname;
397
+ char *path;
398
+ enum branch_checkout_kind kind;
399
+};
400
+
401
+static struct checked_out_branch *checked_out_branches;
402
+static size_t checked_out_branches_alloc, checked_out_branches_nr;
403
+
404
+static void register_checked_out_branch(const char *prefix, const char *name,
405
+ const char *path,
406
+ enum branch_checkout_kind kind)
407
+{
408
+ char *refname = xstrfmt("%s%s", prefix, name);
409
+ char *path_copy = xstrdup(path);
410
+
411
+ ALLOC_GROW(checked_out_branches, checked_out_branches_nr + 1,
412
+ checked_out_branches_alloc);
413
+ checked_out_branches[checked_out_branches_nr].refname = refname;
414
+ checked_out_branches[checked_out_branches_nr].path = path_copy;
415
+ checked_out_branches[checked_out_branches_nr].kind = kind;
416
+ checked_out_branches_nr++;
417
+
418
+ strmap_put(¤t_checked_out_branches, refname, path_copy);
419
+}
420
+
421
static void prepare_checked_out_branches(void)
422
{
423
int i = 0;
@@ -397,7 +430,7 @@ static void prepare_checked_out_branches(void)
430
worktrees = get_worktrees();
431
432
while (worktrees[i]) {
400
- char *old, *wt_gitdir;
433
+ char *wt_gitdir;
434
struct wt_status_state state = { 0 };
435
struct worktree *wt = worktrees[i++];
436
struct string_list update_refs = STRING_LIST_INIT_DUP;
@@ -406,34 +439,25 @@ static void prepare_checked_out_branches(void)
439
continue;
440
441
if (wt->head_ref) {
409
- old = strmap_put(¤t_checked_out_branches,
410
- wt->head_ref,
411
- xstrdup(wt->path));
412
- free(old);
442
+ register_checked_out_branch("", wt->head_ref, wt->path,
443
+ BRANCH_CHECKOUT_KIND_CHECKOUT);
444
}
445
446
if (wt_status_check_rebase(wt, &state) &&
447
(state.rebase_in_progress || state.rebase_interactive_in_progress) &&
448
state.branch) {
418
- struct strbuf ref = STRBUF_INIT;
419
- strbuf_addf(&ref, "refs/heads/%s", state.branch);
420
- old = strmap_put(¤t_checked_out_branches,
421
- ref.buf,
422
- xstrdup(wt->path));
423
- free(old);
424
- strbuf_release(&ref);
449
+ register_checked_out_branch("refs/heads/", state.branch,
450
+ wt->path,
451
+ BRANCH_CHECKOUT_KIND_REBASE);
452
}
453
wt_status_state_free_buffers(&state);
454
455
if (wt_status_check_bisect(wt, &state) &&
456
state.bisecting_from) {
430
- struct strbuf ref = STRBUF_INIT;
431
- strbuf_addf(&ref, "refs/heads/%s", state.bisecting_from);
432
- old = strmap_put(¤t_checked_out_branches,
433
- ref.buf,
434
- xstrdup(wt->path));
435
- free(old);
436
- strbuf_release(&ref);
457
+ register_checked_out_branch("refs/heads/",
458
+ state.bisecting_from,
459
+ wt->path,
460
+ BRANCH_CHECKOUT_KIND_BISECT);
461
}
462
wt_status_state_free_buffers(&state);
463
@@ -442,10 +466,9 @@ static void prepare_checked_out_branches(void)
466
&update_refs)) {
467
struct string_list_item *item;
468
for_each_string_list_item(item, &update_refs) {
445
- old = strmap_put(¤t_checked_out_branches,
446
- item->string,
447
- xstrdup(wt->path));
448
- free(old);
469
+ register_checked_out_branch("", item->string,
470
+ wt->path,
471
+ BRANCH_CHECKOUT_KIND_UPDATE_REF);
472
}
473
string_list_clear(&update_refs, 1);
474
}
@@ -462,6 +485,17 @@ const char *branch_checked_out(const char *refname)
485
return strmap_get(¤t_checked_out_branches, refname);
486
}
487
488
+const char *branch_bisecting(const char *refname)
489
+{
490
+ prepare_checked_out_branches();
491
+ for (size_t i = 0; i < checked_out_branches_nr; i++) {
492
+ if (!strcmp(refname, checked_out_branches[i].refname) &&
493
+ checked_out_branches[i].kind == BRANCH_CHECKOUT_KIND_BISECT)
494
+ return checked_out_branches[i].path;
495
+ }
496
+ return NULL;
497
+}
498
+
499
/*
500
* Check if a branch 'name' can be created as a new branch; die otherwise.
501
* 'force' can be used when it is OK for the named branch already exists.
branch.h
+6
@@ -106,6 +106,12 @@ void create_branches_recursively(struct repository *r, const char *name,
106
*/
107
const char *branch_checked_out(const char *refname);
108
109
+/*
110
+ * If the branch at 'refname' is currently used for bisecting in a
111
+ * worktree, then return the path to that worktree.
112
+ */
113
+const char *branch_bisecting(const char *refname);
114
+
115
/*
116
* Check if 'name' can be a valid name for a branch; die otherwise.
117
* Return 1 if the named branch already exists; return 0 otherwise.
builtin/branch.c
+7
@@ -265,6 +265,13 @@ static int delete_branches(int argc, const char **argv, int force, int kinds,
265
266
if (kinds == FILTER_REFS_BRANCHES) {
267
const char *path;
268
+ if ((path = branch_bisecting(name))) {
269
+ error(_("cannot delete branch '%s' "
270
+ "used by worktree at '%s' for bisect"),
271
+ bname.buf, path);
272
+ ret = 1;
273
+ continue;
274
+ }
275
if ((path = branch_checked_out(name))) {
276
error(_("cannot delete branch '%s' "
277
"used by worktree at '%s'"),
t/t3200-branch.sh
+2
-2
@@ -930,7 +930,7 @@ test_expect_success 'deleting currently checked out branch fails' '
930
git worktree add -b my7 my7 &&
931
test_must_fail git -C my7 branch -d my7 &&
932
test_must_fail git branch -d my7 2>actual &&
933
- test_grep "^error: cannot delete branch .my7. used by worktree at " actual &&
933
+ test_grep "^error: cannot delete branch '"'"'my7'"'"' used by worktree at '"'.*'\$"'" actual &&
934
rm -r my7 &&
935
git worktree prune
936
'
@@ -941,7 +941,7 @@ test_expect_success 'deleting in-use branch fails' '
941
git -C my7 bisect start HEAD HEAD~2 &&
942
test_must_fail git -C my7 branch -d my7 &&
943
test_must_fail git branch -d my7 2>actual &&
944
- test_grep "^error: cannot delete branch .my7. used by worktree at " actual &&
944
+ test_grep "^error: cannot delete branch '"'"'my7'"'"' used by worktree at '"'.*' for bisect\$"'" actual &&
945
rm -r my7 &&
946
git worktree prune
947
'