]> git.ipfire.org Git - thirdparty/git.git/commitdiff
branch: report active bisect run when rejecting delete
authorRené Scharfe <l.s.r@web.de>
Sat, 25 Jul 2026 10:41:07 +0000 (12:41 +0200)
committerJunio C Hamano <gitster@pobox.com>
Sat, 25 Jul 2026 16:22:03 +0000 (09:22 -0700)
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>
branch.c
branch.h
builtin/branch.c
t/t3200-branch.sh

index 243db7d0fc02266b9ec79cd8cb90f620c0ce1970..a9fc79081847f078c1449b0d95da986693b1c77c 100644 (file)
--- 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.
index 3dc6e2a0ffe6359eecd9ca169ff4483e8880a256..e9b1f7b37df06f58842f924fad2d26604c265c31 100644 (file)
--- 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.
index dede60d27b69ead1ba44741ed59737646195050c..29e4ec6c672f75929dbf0962952643247f28c311 100644 (file)
@@ -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'"),
index 1ecbafbee18e0348d636d2ca68ec2f62ae1510fc..051434d9c6ae99c6a1a584615f8145f0a8afcb0f 100755 (executable)
@@ -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
 '