git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH 2/2] ref-filter: name the worktree of a branch being rebased or bisected

From
JMJasper Mayone <me@jaspermayone.com>
Date
Oct 30, 2026, 14:21 UTC
Message-ID
<20261030142137.54750-3-me@jaspermayone.com>
In-Reply-To
<20261030142137.54750-1-me@jaspermayone.com>

%(worktreepath) gives the worktree that has a branch checked out, and "git branch" uses it to mark such branches with "+". It only looks at the HEAD of each worktree, so while a worktree is rebasing or bisecting a branch, its HEAD is detached and the branch shows no worktree at all.

The rest of Git already treats that branch as checked out: "git worktree add" and "git branch -d" refuse it, because finishing the rebase or bisect will check it out again. "git worktree list" shows such a worktree as "(detached HEAD)", so %(worktreepath) is the only place a script could find out which worktree is working on the branch.

Also map the branch that a worktree is rebasing or bisecting to that worktree, as prepare_checked_out_branches() in branch.c does. Branches that "rebase --update-refs" will update are not mapped: that worktree has not checked them out, it only moves them when the rebase is done.

As a result "git branch" marks such a branch with "+", also when it runs in the worktree that is rebasing or bisecting it, where it shows both "* (no branch, rebasing topic)" and "+ topic". Document that.

Reported-by: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>
Helped-by: Phillip Wood <phillip.wood123@gmail.com>
Signed-off-by: Jasper Mayone <me@jaspermayone.com>
---
 Documentation/git-branch.adoc       |  5 ++-
 Documentation/git-for-each-ref.adoc |  4 +-
 ref-filter.c                        | 60 +++++++++++++++++++++------
 t/t3203-branch-output.sh            | 35 ++++++++++++++++
 t/t6302-for-each-ref-filter.sh      | 63 +++++++++++++++++++++++++++++
 5 files changed, 153 insertions(+), 14 deletions(-)
diff --git a/Documentation/git-branch.adoc b/Documentation/git-branch.adoc
index bfdf459329..d499d7e0d4 100644
--- a/Documentation/git-branch.adoc
+++ b/Documentation/git-branch.adoc
@@ -33,7 +33,10 @@ DESCRIPTION
 If `--list` is given, or if there are no non-option arguments, existing
 branches are listed; the current branch will be highlighted in green and
 marked with an asterisk.  Any branches checked out in linked worktrees will
-be highlighted in cyan and marked with a plus sign. Option `-r` causes the
+be highlighted in cyan and marked with a plus sign. A branch that a
+worktree is rebasing or bisecting counts as checked out there, so it is
+marked in the same way, also in the worktree that is rebasing or
+bisecting it. Option `-r` causes the
 remote-tracking branches to be listed,
 and option `-a` shows both local and remote branches.
 
diff --git a/Documentation/git-for-each-ref.adoc b/Documentation/git-for-each-ref.adoc
index a7b31e8aaf..90ff37259e 100644
--- a/Documentation/git-for-each-ref.adoc
+++ b/Documentation/git-for-each-ref.adoc
@@ -182,7 +182,9 @@ but if used together the last option is selected.
 
 `worktreepath`::
 	The absolute path to the worktree in which the ref is checked
-	out, if it is checked out in any linked worktree. Empty string
+	out, if it is checked out in any linked worktree. A branch that
+	a worktree is rebasing or bisecting counts as checked out there,
+	although that worktree's `HEAD` is detached. Empty string
 	otherwise.
 
 `ahead-behind:<commit-ish>`::
diff --git a/ref-filter.c b/ref-filter.c
index bdf54f6f59..1aebea243e 100644
--- a/ref-filter.c
+++ b/ref-filter.c
@@ -97,7 +97,8 @@ static struct expand_data {
 
 struct ref_to_worktree_entry {
 	struct hashmap_entry ent;
-	struct worktree *wt; /* key is wt->head_ref */
+	struct worktree *wt;
+	char refname[FLEX_ARRAY]; /* the key */
 };
 
 static int ref_to_worktree_map_cmpfnc(const void *lookupdata UNUSED,
@@ -110,8 +111,8 @@ static int ref_to_worktree_map_cmpfnc(const void *lookupdata UNUSED,
 	e = container_of(eptr, const struct ref_to_worktree_entry, ent);
 	k = container_of(kptr, const struct ref_to_worktree_entry, ent);
 
-	return strcmp(e->wt->head_ref,
-		keydata_aka_refname ? keydata_aka_refname : k->wt->head_ref);
+	return strcmp(e->refname,
+		keydata_aka_refname ? keydata_aka_refname : k->refname);
 }
 
 static struct ref_to_worktree_map {
@@ -2380,21 +2381,56 @@ static int get_object(struct ref_array_item *ref, int deref,
 	return ret;
 }
 
+static void add_worktree_entry(struct hashmap *map, struct worktree *wt,
+			       const char *refname)
+{
+	struct ref_to_worktree_entry *entry;
+
+	FLEX_ALLOC_STR(entry, refname, refname);
+	entry->wt = wt;
+	hashmap_entry_init(&entry->ent, strhash(refname));
+	hashmap_add(map, &entry->ent);
+}
+
+/*
+ * Map each branch to the worktree that has it checked out. A worktree
+ * that is rebasing or bisecting a branch has it checked out, too,
+ * even though its HEAD may be detached, as for
+ * prepare_checked_out_branches() in branch.c.
+ */
 static void populate_worktree_map(struct hashmap *map, struct worktree **worktrees)
 {
-	int i;
+	struct strbuf ref = STRBUF_INIT;
 
-	for (i = 0; worktrees[i]; i++) {
-		if (worktrees[i]->head_ref) {
-			struct ref_to_worktree_entry *entry;
-			entry = xmalloc(sizeof(*entry));
-			entry->wt = worktrees[i];
-			hashmap_entry_init(&entry->ent,
-					strhash(worktrees[i]->head_ref));
+	for (size_t i = 0; worktrees[i]; i++) {
+		struct worktree *wt = worktrees[i];
+		struct wt_status_state state = { 0 };
 
-			hashmap_add(map, &entry->ent);
+		if (wt->is_bare)
+			continue;
+
+		if (wt->head_ref)
+			add_worktree_entry(map, wt, wt->head_ref);
+
+		if (wt_status_check_rebase(wt, &state) &&
+		    (state.rebase_in_progress ||
+		     state.rebase_interactive_in_progress) &&
+		    state.branch) {
+			strbuf_reset(&ref);
+			strbuf_addf(&ref, "refs/heads/%s", state.branch);
+			add_worktree_entry(map, wt, ref.buf);
 		}
+		wt_status_state_free_buffers(&state);
+
+		if (wt_status_check_bisect(wt, &state) &&
+		    state.bisecting_from) {
+			strbuf_reset(&ref);
+			strbuf_addf(&ref, "refs/heads/%s", state.bisecting_from);
+			add_worktree_entry(map, wt, ref.buf);
+		}
+		wt_status_state_free_buffers(&state);
 	}
+	strbuf_release(&ref);
 }
 
 static void lazy_init_worktree_map(void)
diff --git a/t/t3203-branch-output.sh b/t/t3203-branch-output.sh
index 7c44186341..e04599392d 100755
--- a/t/t3203-branch-output.sh
+++ b/t/t3203-branch-output.sh
@@ -442,6 +442,41 @@ test_expect_success 'worktree colors correct' '
 	test_cmp expect actual
 '
 
+test_expect_success 'branch being rebased in a worktree is marked' '
+	test_when_finished "git -C rebase_dir rebase --abort; rm -rf rebase_dir; git worktree prune; git branch -D rebasing" &&
+	git worktree add -b rebasing rebase_dir branch-two &&
+	git -C rebase_dir commit --allow-empty -m rebase-me &&
+	test_must_fail git -C rebase_dir rebase -f -x false HEAD~1 &&
+	test_must_fail git -C rebase_dir symbolic-ref -q HEAD &&
+	git branch --list rebasing >actual &&
+	echo "+ rebasing" >expect &&
+	test_cmp expect actual
+'
+
+test_expect_success 'branch being rebased is marked in its own worktree' '
+	test_when_finished "git -C rebase_dir rebase --abort; rm -rf rebase_dir; git worktree prune; git branch -D rebasing" &&
+	git worktree add -b rebasing rebase_dir branch-two &&
+	git -C rebase_dir commit --allow-empty -m rebase-me &&
+	test_must_fail git -C rebase_dir rebase -f -x false HEAD~1 &&
+	git -C rebase_dir branch --list rebasing >actual &&
+	echo "+ rebasing" >expect &&
+	test_cmp expect actual &&
+	git -C rebase_dir branch >actual &&
+	test_grep "^\* (no branch, rebasing rebasing)" actual
+'
+
+test_expect_success 'branch being bisected in a worktree is marked' '
+	test_when_finished "git -C bisect_dir bisect reset; rm -rf bisect_dir; git worktree prune; git branch -D bisecting" &&
+	git worktree add -b bisecting bisect_dir branch-two &&
+	git -C bisect_dir commit --allow-empty -m bisect-one &&
+	git -C bisect_dir commit --allow-empty -m bisect-two &&
+	git -C bisect_dir bisect start HEAD HEAD~2 &&
+	test_must_fail git -C bisect_dir symbolic-ref -q HEAD &&
+	git branch --list bisecting >actual &&
+	echo "+ bisecting" >expect &&
+	test_cmp expect actual
+'
+
 test_expect_success "set up color tests" '
 	echo "<RED>main<RESET>" >expect.color &&
 	echo "main" >expect.bare &&
diff --git a/t/t6302-for-each-ref-filter.sh b/t/t6302-for-each-ref-filter.sh
index 7f060d97bf..d656901f21 100755
--- a/t/t6302-for-each-ref-filter.sh
+++ b/t/t6302-for-each-ref-filter.sh
@@ -3,6 +3,7 @@
 test_description='test for-each-refs usage of ref-filter APIs'
 
 . ./test-lib.sh
+. "$TEST_DIRECTORY"/lib-rebase.sh
 . "$TEST_DIRECTORY"/lib-gpg.sh
 
 test_expect_success 'setup some history and refs' '
@@ -541,6 +542,68 @@ test_expect_success 'validate worktree atom' '
 	test_cmp expect actual
 '
 
+test_expect_success 'worktree atom names a worktree rebasing the branch' '
+	test_when_finished "git -C rebasing rebase --abort; rm -rf rebasing; git worktree prune; git branch -D rebasing" &&
+	git worktree add -b rebasing rebasing main &&
+	git -C rebasing commit --allow-empty -m rebase-me &&
+	test_must_fail git -C rebasing rebase -f -x false main &&
+	test_must_fail git -C rebasing symbolic-ref -q HEAD &&
+	echo "$(pwd)/rebasing" >expect &&
+	git for-each-ref --format="%(worktreepath)" refs/heads/rebasing >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'worktree atom names a worktree bisecting the branch' '
+	test_when_finished "git -C bisecting bisect reset; rm -rf bisecting; git worktree prune; git branch -D bisecting" &&
+	git worktree add -b bisecting bisecting main &&
+	git -C bisecting commit --allow-empty -m bisect-one &&
+	git -C bisecting commit --allow-empty -m bisect-two &&
+	git -C bisecting bisect start HEAD main &&
+	test_must_fail git -C bisecting symbolic-ref -q HEAD &&
+	echo "$(pwd)/bisecting" >expect &&
+	git for-each-ref --format="%(worktreepath)" refs/heads/bisecting >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'worktree atom with an interactive rebase stopped at edit' '
+	test_when_finished "git -C editing rebase --abort; rm -rf editing; git worktree prune; git branch -D editing" &&
+	git worktree add -b editing editing main &&
+	git -C editing commit --allow-empty -m edit-me &&
+	(
+		set_fake_editor &&
+		FAKE_LINES="edit 1" git -C editing rebase -i --keep-empty HEAD~1
+	) &&
+	test_must_fail git -C editing symbolic-ref -q HEAD &&
+	echo "$(pwd)/editing" >expect &&
+	git for-each-ref --format="%(worktreepath)" refs/heads/editing >actual &&
+	test_cmp expect actual
+'
+
+test_expect_success 'worktree atom ignores branches rebase --update-refs will move' '
+	test_when_finished "git -C updating rebase --abort; rm -rf updating; git worktree prune; git branch -D updating mid" &&
+	git worktree add -b updating updating main &&
+	git -C updating commit --allow-empty -m one &&
+	git -C updating branch mid &&
+	git -C updating commit --allow-empty -m two &&
+	test_must_fail git -C updating rebase -f --update-refs -x false main &&
+	git for-each-ref --format="%(refname:short) [%(worktreepath)]" \
+		refs/heads/updating refs/heads/mid >actual &&
+	cat >expect <<-EOF &&
+	mid []
+	updating [$(pwd)/updating]
+	EOF
+	test_cmp expect actual
+'
+
+test_expect_success 'worktree atom ignores a rebase of a detached HEAD' '
+	test_when_finished "git -C detached-rebase rebase --abort; rm -rf detached-rebase; git worktree prune" &&
+	git worktree add --detach detached-rebase main &&
+	git -C detached-rebase commit --allow-empty -m detached &&
+	test_must_fail git -C detached-rebase rebase -f -x false main &&
+	git for-each-ref --format="%(worktreepath)" refs/heads/ >actual &&
+	test_grep ! detached-rebase actual
+'
+
 test_expect_success 'start after with empty value' '
 	cat >expect <<-\EOF &&
 	refs/heads/main
-- 
2.56.0
Previous: Jasper MayoneNext: Phillip Wood
Message 20 of 33 in “worktree list: add --merged and --no-merged”
  1. 0/1 worktree list: add --merged and --no-mergedJasper Mayone, Oct 21, 2026
  2. 1/1 worktree list: add --merged and --no-mergedJasper Mayone, Oct 21, 2026
  3. Junio C HamanoOct 23, 2026
  4. Eric SunshineOct 24, 2026
  5. worktree list: add --branch to select worktrees by branchJasper Mayone, Oct 24, 2026
  6. Phillip WoodOct 25, 2026
  7. Phillip WoodOct 26, 2026
  8. Eric SunshineOct 28, 2026
  9. Junio C HamanoOct 28, 2026
  10. Kristoffer HaugsbakkOct 28, 2026
  11. Jasper MayoneOct 29, 2026
  12. Eric SunshineOct 29, 2026
  13. Phillip WoodOct 29, 2026
  14. Ben KnobleOct 29, 2026
  15. Phillip WoodOct 29, 2026
  16. 0/2 worktree: treat a branch being rebased or bisected as checked outJasper Mayone, Oct 30, 2026
  17. 1/2 worktree remove: refuse a worktree in the middle of a rebase or bisectJasper Mayone, Oct 30, 2026
  18. Phillip WoodNov 2, 2026
  19. Jasper MayoneNov 2, 2026
  20. 2/2 ref-filter: name the worktree of a branch being rebased or bisectedJasper Mayone, Oct 30, 2026
  21. Phillip WoodNov 2, 2026
  22. Jasper MayoneNov 2, 2026
  23. Junio C HamanoOct 30, 2026
  24. Jasper MayoneOct 30, 2026
  25. 0/4 worktree: treat a branch being rebased or bisected as checked outJasper Mayone, Nov 2, 2026
  26. 1/4 worktree remove: refuse a worktree in the middle of an operationJasper Mayone, Nov 2, 2026
  27. 2/4 doc: for-each-ref: say how to read %(worktreepath) safelyJasper Mayone, Nov 2, 2026
  28. 3/4 ref-filter: name the worktree of a branch being rebased or bisectedJasper Mayone, Nov 2, 2026
  29. 4/4 branch: do not mark the branch the current worktree is rebasingJasper Mayone, Nov 2, 2026
  30. Harald NordgrenOct 21, 2026
  31. Junio C HamanoOct 21, 2026
  32. Harald NordgrenOct 22, 2026
  33. Kristoffer HaugsbakkOct 22, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.