[PATCH v2] branch: report active bisect run when rejecting delete

René Scharfe <[email protected]>
Newsgroups org.kernel.vger.git
Message-ID <[email protected]>
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 <[email protected]>
Signed-off-by: René Scharfe <[email protected]>
---
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
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.