Git development
 help / color / mirror / Atom feed
From: Jasper Mayone <me@jaspermayone.com>
To: git@vger.kernel.org
Cc: gitster@pobox.com, phillip.wood123@gmail.com,
	sunshine@sunshineco.com, kristofferhaugsbakk@fastmail.com,
	ben.knoble@gmail.com, haraldnordgren@gmail.com,
	Jasper Mayone <me@jaspermayone.com>
Subject: [PATCH v4 0/4] worktree: treat a branch being rebased or bisected as checked out
Date: Mon,  2 Nov 2026 13:29:26 -0500	[thread overview]
Message-ID: <20261102182930.3511-1-me@jaspermayone.com> (raw)
In-Reply-To: <20261030142137.54750-1-me@jaspermayone.com>

While a worktree is rebasing or bisecting a branch, its HEAD is
detached. "git worktree add" and "git branch -d" already treat the
branch as checked out there, but "git worktree remove" deletes such a
worktree when its files are clean, and %(worktreepath) and "git
branch" do not show which worktree has the branch.

Patch 1 makes "git worktree remove" refuse a worktree in the middle of
an operation unless --force is given. Patch 2 documents how to read
%(worktreepath) safely. Patch 3 makes %(worktreepath) name the
worktree that is rebasing or bisecting a branch, and patch 4 keeps
"git branch" from marking that branch with "+" in that worktree.

Changes since v3 [1]:

 * Patch 1: also refuse a worktree that has CHERRY_PICK_HEAD or
   REVERT_HEAD, for a single cherry-pick or revert whose files are
   clean, with tests. The git-worktree documentation now lists the
   cases that need --force. The tests use a shared setup and
   subshells. Dropped the Helped-by trailer, as Phillip asked.

 * Patch 2 is new. It adds the notes that Phillip suggested to the
   documentation of %(worktreepath): the path can contain a newline,
   and only one worktree is shown for a branch that is checked out in
   more than one.

 * Patch 3: %(worktreepath) now uses a new branch.c function,
   branch_checked_out_worktree(), that reads the list that
   prepare_checked_out_branches() builds, in place of a copy of that
   code in ref-filter.c. ref-filter.c loses its own worktree map. The
   commit message explains why branches that "rebase --update-refs"
   will update are not included, and the change for the HEAD of a
   bare repository, which now has a test.

 * Patch 4 is new. "git branch" no longer marks a branch with "+" in
   the worktree that is rebasing or bisecting it, because the
   "(no branch, ...)" line already names it.

[1] https://lore.kernel.org/git/20261030142137.54750-1-me@jaspermayone.com/

Jasper Mayone (4):
  worktree remove: refuse a worktree in the middle of an operation
  doc: for-each-ref: say how to read %(worktreepath) safely
  ref-filter: name the worktree of a branch being rebased or bisected
  branch: do not mark the branch the current worktree is rebasing

 Documentation/git-branch.adoc       |   6 +-
 Documentation/git-for-each-ref.adoc |  12 +++-
 Documentation/git-worktree.adoc     |  17 +++--
 branch.c                            |  13 ++++
 branch.h                            |   9 +++
 builtin/branch.c                    | 103 +++++++++++++++++++++-------
 builtin/worktree.c                  |  37 +++++++++-
 ref-filter.c                        |  74 +-------------------
 t/t2403-worktree-move.sh            |  94 +++++++++++++++++++++++++
 t/t3203-branch-output.sh            |  49 +++++++++++++
 t/t6302-for-each-ref-filter.sh      |  76 ++++++++++++++++++++
 11 files changed, 386 insertions(+), 104 deletions(-)

Range-diff against v3:
1:  a79b69a0e4 ! 1:  bca692b844 worktree remove: refuse a worktree in the middle of a rebase or bisect
    @@ Metadata
     Author: Jasper Mayone <me@jaspermayone.com>
     
      ## Commit message ##
    -    worktree remove: refuse a worktree in the middle of a rebase or bisect
    +    worktree remove: refuse a worktree in the middle of an operation
     
    -    "git worktree remove" refuses a worktree with modified or untracked
    -    files unless --force is given, but it removes a worktree that is in
    -    the middle of a rebase, a "git am" session, a bisect or a cherry-pick
    -    or revert sequence when its files happen to be clean. The state of
    -    that operation lives in the worktree's own $GIT_DIR, so it is lost
    -    with the worktree. A rebase stopped at an "edit" or "break" line, an
    -    "exec" that failed, or a cherry-pick sequence after a conflict was
    -    resolved and committed is clean in this sense and still has work in
    -    progress.
    +    "git worktree remove" refuses to remove a worktree with modified or
    +    untracked files unless --force is given, but it removes a worktree
    +    that is in the middle of a rebase, a "git am" session, a bisect, a
    +    cherry-pick or a revert when its files happen to be clean. The state
    +    of that operation lives in the worktree's own $GIT_DIR, so it is lost
    +    with the worktree. Examples of such a clean worktree are a rebase
    +    stopped at an "edit" or "break" line or after a failed "exec", a
    +    cherry-pick sequence after a conflict was resolved and committed, and
    +    a single cherry-pick or revert whose conflict was resolved to the
    +    current contents, which leaves CHERRY_PICK_HEAD or REVERT_HEAD behind.
     
         Treat such a worktree as unclean: refuse to remove it unless --force
         is given, and name the operation in the message.
    @@ Commit message
         worktree, which is only done when the directory exists.
     
         Reported-by: D. Ben Knoble <ben.knoble@gmail.com>
    -    Helped-by: Phillip Wood <phillip.wood123@gmail.com>
         Signed-off-by: Jasper Mayone <me@jaspermayone.com>
     
      ## Documentation/git-worktree.adoc ##
     @@ Documentation/git-worktree.adoc: the new working tree location.
    + 
      `remove`::
      
    - Remove a worktree. Only clean worktrees (no untracked files and no
    +-Remove a worktree. Only clean worktrees (no untracked files and no
     -modification in tracked files) can be removed. Unclean worktrees or ones
     -with submodules can be removed with `--force`. The main worktree cannot be
     -removed.
    -+modification in tracked files) can be removed. A worktree in the middle
    -+of a rebase, a `git am` session, a bisect or a cherry-pick or revert
    -+sequence is not clean either, because removing it loses that state. Unclean worktrees or ones with submodules
    -+can be removed with `--force`. The main worktree cannot be removed.
    ++Remove a worktree. The main worktree cannot be removed. Without
    ++`--force`, a worktree is not removed when:
    +++
    ++--
    ++* it has untracked files or modifications in tracked files,
    ++* it has submodules, or
    ++* it is in the middle of a rebase, a `git am` session, a bisect, a
    ++  cherry-pick or a revert, because removing it loses the state of that
    ++  operation.
    ++--
      
      `repair [<path>...]`::
      
    @@ Documentation/git-worktree.adoc: twice. If the destination is already assigned t
      allows the move to proceed; use `--force` twice if the destination is locked.
      +
     -`remove` refuses to remove an unclean worktree unless `--force` is used.
    -+`remove` refuses to remove an unclean worktree, or one in the middle of a
    -+rebase, `git am` session, bisect, or cherry-pick or revert sequence,
    -+unless `--force` is used.
    ++`remove` refuses to remove a worktree in the cases listed under
    ++`remove` above unless `--force` is used.
      To remove a locked worktree, specify `--force` twice.
      
      `-b <new-branch>`::
    @@ builtin/worktree.c: static void check_clean_worktree(struct worktree *wt,
      }
      
     +/*
    -+ * A rebase, "git am", bisect or cherry-pick/revert sequence keeps its
    -+ * state in the worktree's own $GIT_DIR, and removing the worktree
    -+ * throws that away even when "git status" says the files are clean.
    ++ * A rebase, "git am", bisect, cherry-pick or revert keeps its state in
    ++ * the worktree's own $GIT_DIR, and removing the worktree throws that
    ++ * away even when "git status" says the files are clean.
     + */
     +static void check_no_operation_in_progress(struct worktree *wt,
     +					   const char *original_path)
    @@ builtin/worktree.c: static void check_clean_worktree(struct worktree *wt,
     +		die(_("'%s' is in the middle of a bisect, use --force to delete it"),
     +		    original_path);
     +	wt_status_state_free_buffers(&state);
    ++	if (refs_ref_exists(get_worktree_ref_store(wt), "CHERRY_PICK_HEAD"))
    ++		die(_("'%s' is in the middle of a cherry-pick, use --force to delete it"),
    ++		    original_path);
    ++	if (refs_ref_exists(get_worktree_ref_store(wt), "REVERT_HEAD"))
    ++		die(_("'%s' is in the middle of a revert, use --force to delete it"),
    ++		    original_path);
     +	if (is_directory(worktree_git_path(wt, "sequencer")))
     +		die(_("'%s' is in the middle of a cherry-pick or revert, use --force to delete it"),
     +		    original_path);
    @@ t/t2403-worktree-move.sh: test_expect_success 'move worktree with relative path
      	test_cmp expect .git/worktrees/absolute/gitdir
      '
      
    ++test_expect_success 'setup commits that conflict for am, cherry-pick and revert' '
    ++	git worktree add --detach conflict-setup init &&
    ++	(
    ++		cd conflict-setup &&
    ++		test_commit base conflict.t base &&
    ++		git switch --detach init &&
    ++		test_commit side conflict.t side &&
    ++		test_commit side-two
    ++	) &&
    ++	git worktree remove conflict-setup
    ++'
    ++
     +test_expect_success 'remove worktree in the middle of a rebase' '
     +	test_when_finished "rm -rf mid-rebase; git worktree prune" &&
    -+	git worktree add --detach mid-rebase init &&
    -+	test_commit -C mid-rebase rebase-me &&
    ++	git worktree add --detach mid-rebase side &&
     +	test_must_fail git -C mid-rebase rebase -f -x false init &&
     +	test_must_fail git worktree remove mid-rebase 2>err &&
     +	test_grep "in the middle of a rebase" err &&
    @@ t/t2403-worktree-move.sh: test_expect_success 'move worktree with relative path
     +
     +test_expect_success 'remove worktree in the middle of an am session' '
     +	test_when_finished "rm -rf mid-am patch; git worktree prune" &&
    -+	git worktree add --detach mid-am init &&
    -+	test_commit -C mid-am am-me &&
    -+	git -C mid-am format-patch -1 --stdout >patch &&
    -+	git -C mid-am reset --hard init &&
    -+	echo conflict >mid-am/am-me.t &&
    -+	git -C mid-am add am-me.t &&
    -+	git -C mid-am commit -m conflict &&
    -+	test_must_fail git -C mid-am am ../patch &&
    -+	git -C mid-am reset --hard &&
    ++	git format-patch -1 --stdout side >patch &&
    ++	git worktree add --detach mid-am base &&
    ++	(
    ++		cd mid-am &&
    ++		test_must_fail git am ../patch &&
    ++		git reset --hard
    ++	) &&
     +	test_must_fail git worktree remove mid-am 2>err &&
     +	test_grep "in the middle of an am session" err &&
     +	git worktree remove --force mid-am
    @@ t/t2403-worktree-move.sh: test_expect_success 'move worktree with relative path
     +
     +test_expect_success 'remove worktree in the middle of a bisect' '
     +	test_when_finished "rm -rf mid-bisect; git worktree prune" &&
    -+	git worktree add --detach mid-bisect init &&
    -+	test_commit -C mid-bisect bisect-one &&
    -+	test_commit -C mid-bisect bisect-two &&
    -+	git -C mid-bisect bisect start HEAD init &&
    ++	git worktree add --detach mid-bisect side-two &&
    ++	git -C mid-bisect bisect start side-two init &&
     +	test_must_fail git worktree remove mid-bisect 2>err &&
     +	test_grep "in the middle of a bisect" err &&
     +	git worktree remove --force mid-bisect
     +'
     +
    -+test_expect_success 'remove worktree in the middle of a cherry-pick sequence' '
    ++test_expect_success 'remove worktree in the middle of a cherry-pick' '
     +	test_when_finished "rm -rf mid-pick; git worktree prune" &&
    -+	git worktree add --detach mid-pick init &&
    -+	git -C mid-pick switch -c pick-src &&
    -+	test_commit -C mid-pick pick-one &&
    -+	test_commit -C mid-pick pick-two &&
    -+	git -C mid-pick switch --detach init &&
    -+	echo conflict >mid-pick/pick-one.t &&
    -+	git -C mid-pick add pick-one.t &&
    -+	git -C mid-pick commit -m conflict &&
    -+	test_must_fail git -C mid-pick cherry-pick pick-src~1 pick-src &&
    -+	git -C mid-pick checkout --theirs pick-one.t &&
    -+	git -C mid-pick add pick-one.t &&
    -+	git -C mid-pick commit --no-edit &&
    -+	test_path_is_dir .git/worktrees/mid-pick/sequencer &&
    ++	git worktree add --detach mid-pick base &&
    ++	(
    ++		cd mid-pick &&
    ++		test_must_fail git cherry-pick side &&
    ++		git checkout --ours conflict.t &&
    ++		git add conflict.t &&
    ++		git rev-parse --verify CHERRY_PICK_HEAD
    ++	) &&
    ++	test_path_is_missing .git/worktrees/mid-pick/sequencer &&
     +	test_must_fail git worktree remove mid-pick 2>err &&
    -+	test_grep "in the middle of a cherry-pick or revert" err &&
    ++	test_grep "in the middle of a cherry-pick," err &&
     +	git worktree remove --force mid-pick
     +'
    ++
    ++test_expect_success 'remove worktree in the middle of a revert' '
    ++	test_when_finished "rm -rf mid-revert; git worktree prune" &&
    ++	git worktree add --detach mid-revert base &&
    ++	(
    ++		cd mid-revert &&
    ++		test_must_fail git revert side &&
    ++		git checkout --ours conflict.t &&
    ++		git add conflict.t &&
    ++		git rev-parse --verify REVERT_HEAD
    ++	) &&
    ++	test_path_is_missing .git/worktrees/mid-revert/sequencer &&
    ++	test_must_fail git worktree remove mid-revert 2>err &&
    ++	test_grep "in the middle of a revert" err &&
    ++	git worktree remove --force mid-revert
    ++'
    ++
    ++test_expect_success 'remove worktree in the middle of a cherry-pick sequence' '
    ++	test_when_finished "rm -rf mid-pick-seq; git worktree prune" &&
    ++	git worktree add --detach mid-pick-seq base &&
    ++	(
    ++		cd mid-pick-seq &&
    ++		test_must_fail git cherry-pick side side-two &&
    ++		git checkout --theirs conflict.t &&
    ++		git add conflict.t &&
    ++		git commit --no-edit
    ++	) &&
    ++	test_path_is_dir .git/worktrees/mid-pick-seq/sequencer &&
    ++	test_must_fail git worktree remove mid-pick-seq 2>err &&
    ++	test_grep "in the middle of a cherry-pick or revert" err &&
    ++	git worktree remove --force mid-pick-seq
    ++'
     +
      test_done
-:  ---------- > 2:  39eb92d441 doc: for-each-ref: say how to read %(worktreepath) safely
2:  0cd13347e1 ! 3:  990a19fd11 ref-filter: name the worktree of a branch being rebased or bisected
    @@ Commit message
     
         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.
    +    rebase or bisect will check it out again. But no command shows which
    +    worktree is working on the branch: "git worktree list" shows such a
    +    worktree as "(detached HEAD)", and %(worktreepath) is empty.
     
    -    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.
    +    branch.c already knows the answer, as prepare_checked_out_branches()
    +    records the branch that each worktree has checked out, is rebasing or
    +    is bisecting. Add branch_checked_out_worktree() to look a branch up in
    +    that list, and use it for %(worktreepath) in place of the map that
    +    ref-filter.c builds from the HEAD of each worktree.
     
    -    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.
    +    The new function skips the branches that "rebase --update-refs" will
    +    update. branch_checked_out() includes them, so that they cannot be
    +    checked out or deleted during the rebase, but the worktree that runs
    +    the rebase has not checked them out. %(worktreepath) therefore stays
    +    empty for them, unlike branch_checked_out().
    +
    +    This also changes the result for a bare repository: its HEAD names a
    +    branch, and %(worktreepath) used to give the path of the bare
    +    repository for that branch. "git branch" in a linked worktree marked
    +    the branch with "+", although no worktree has it checked out and "git
    +    worktree add" lets you check it out. prepare_checked_out_branches()
    +    skips bare repositories, so %(worktreepath) is now empty for that
    +    branch and "git branch" no longer marks it.
     
         Reported-by: Kristoffer Haugsbakk <kristofferhaugsbakk@fastmail.com>
         Helped-by: Phillip Wood <phillip.wood123@gmail.com>
    @@ Documentation/git-branch.adoc: DESCRIPTION
      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
    +-remote-tracking branches to be listed,
     +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,
    ++worktree is rebasing or bisecting counts as checked out in that
    ++worktree, so the other worktrees mark it in the same way. Option `-r`
    ++causes the remote-tracking branches to be listed,
      and option `-a` shows both local and remote branches.
      
    + If a `<pattern>`
     
      ## Documentation/git-for-each-ref.adoc ##
     @@ Documentation/git-for-each-ref.adoc: but if used together the last option is selected.
    @@ Documentation/git-for-each-ref.adoc: but if used together the last option is sel
     -	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>`::
    ++	although that worktree's `HEAD` is detached. A branch that
    ++	`git rebase --update-refs` will update does not. Empty string
    + 	otherwise. If the ref is checked out in more than one worktree,
    + 	only one of them is shown. The path can contain a newline, so
    + 	use one of the quoting options such as `--shell`, or follow
     
    - ## ref-filter.c ##
    -@@ ref-filter.c: 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,
    -@@ ref-filter.c: 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);
    + ## branch.c ##
    +@@ branch.c: const char *branch_checked_out(const char *refname)
    + 	return strmap_get(&current_checked_out_branches, refname);
      }
      
    - static struct ref_to_worktree_map {
    ++const char *branch_checked_out_worktree(const char *refname)
    ++{
    ++	const char *path = NULL;
    ++
    ++	prepare_checked_out_branches();
    ++	for (size_t i = 0; i < checked_out_branches_nr; i++) {
    ++		if (checked_out_branches[i].kind != BRANCH_CHECKOUT_KIND_UPDATE_REF &&
    ++		    !strcmp(refname, checked_out_branches[i].refname))
    ++			path = checked_out_branches[i].path;
    ++	}
    ++	return path;
    ++}
    ++
    + const char *branch_bisecting(const char *refname)
    + {
    + 	prepare_checked_out_branches();
    +
    + ## branch.h ##
    +@@ branch.h: void create_branches_recursively(struct repository *r, const char *name,
    +  */
    + const char *branch_checked_out(const char *refname);
    + 
    ++/*
    ++ * If a worktree has the branch 'refname' checked out, or is rebasing or
    ++ * bisecting it, return the path of that worktree, or of the last one if
    ++ * there are more. Otherwise return NULL. Unlike branch_checked_out(),
    ++ * this ignores the branches that "git rebase --update-refs" will
    ++ * update, because no worktree has them checked out.
    ++ */
    ++const char *branch_checked_out_worktree(const char *refname);
    ++
    + /*
    +  * If the branch at 'refname' is currently used for bisecting in a
    +  * worktree, then return the path to that worktree.
    +
    + ## ref-filter.c ##
    +@@
    + #define DISABLE_SIGN_COMPARE_WARNINGS
    + 
    + #include "git-compat-util.h"
    ++#include "branch.h"
    + #include "environment.h"
    + #include "gettext.h"
    + #include "config.h"
    +@@ ref-filter.c: static struct expand_data {
    + 	struct object_info info;
    + } oi, oi_deref;
    + 
    +-struct ref_to_worktree_entry {
    +-	struct hashmap_entry ent;
    +-	struct worktree *wt; /* key is wt->head_ref */
    +-};
    +-
    +-static int ref_to_worktree_map_cmpfnc(const void *lookupdata UNUSED,
    +-				      const struct hashmap_entry *eptr,
    +-				      const struct hashmap_entry *kptr,
    +-				      const void *keydata_aka_refname)
    +-{
    +-	const struct ref_to_worktree_entry *e, *k;
    +-
    +-	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);
    +-}
    +-
    +-static struct ref_to_worktree_map {
    +-	struct hashmap map;
    +-	struct worktree **worktrees;
    +-} ref_to_worktree_map;
    +-
    + /*
    +  * The enum atom_type is used as the index of valid_atom array.
    +  * In the atom parsing stage, it will be passed to used_atom.atom_type
     @@ ref-filter.c: 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)
    - {
    +-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;
    @@ ref-filter.c: static int get_object(struct ref_array_item *ref, int deref,
     -			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)
    +-{
    +-	if (ref_to_worktree_map.worktrees)
    +-		return;
    +-
    +-	ref_to_worktree_map.worktrees = get_worktrees(the_repository);
    +-	hashmap_init(&(ref_to_worktree_map.map), ref_to_worktree_map_cmpfnc, NULL, 0);
    +-	populate_worktree_map(&(ref_to_worktree_map.map), ref_to_worktree_map.worktrees);
    +-}
    +-
    + static char *get_worktree_path(const struct ref_array_item *ref)
    + {
    +-	struct hashmap_entry entry, *e;
    +-	struct ref_to_worktree_entry *lookup_result;
    ++	const char *path = branch_checked_out_worktree(ref->refname);
    + 
    +-	lazy_init_worktree_map();
    +-
    +-	hashmap_entry_init(&entry, strhash(ref->refname));
    +-	e = hashmap_get(&(ref_to_worktree_map.map), &entry, ref->refname);
    +-
    +-	if (!e)
    +-		return xstrdup("");
    +-
    +-	lookup_result = container_of(e, struct ref_to_worktree_entry, ent);
    +-
    +-	return xstrdup(lookup_result->wt->path);
    ++	return xstrdup(path ? path : "");
    + }
    + 
    + /*
    +@@ ref-filter.c: void ref_array_clear(struct ref_array *array)
    + 	FREE_AND_NULL(used_atom);
    + 	used_atom_cnt = 0;
    + 
    +-	if (ref_to_worktree_map.worktrees) {
    +-		hashmap_clear_and_free(&(ref_to_worktree_map.map),
    +-					struct ref_to_worktree_entry, ent);
    +-		free_worktrees(ref_to_worktree_map.worktrees);
    +-		ref_to_worktree_map.worktrees = NULL;
    +-	}
    +-
    + 	FREE_AND_NULL(array->counts);
      }
      
    - static void lazy_init_worktree_map(void)
     
      ## t/t3203-branch-output.sh ##
     @@ t/t3203-branch-output.sh: test_expect_success 'worktree colors correct' '
    @@ t/t3203-branch-output.sh: test_expect_success 'worktree colors correct' '
     +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 &&
    ++	(
    ++		cd rebase_dir &&
    ++		git commit --allow-empty -m rebase-me &&
    ++		test_must_fail git rebase -f -x false HEAD~1 &&
    ++		test_must_fail git 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 &&
    ++	(
    ++		cd bisect_dir &&
    ++		git commit --allow-empty -m bisect-one &&
    ++		git commit --allow-empty -m bisect-two &&
    ++		git bisect start HEAD HEAD~2 &&
    ++		test_must_fail git symbolic-ref -q HEAD
    ++	) &&
     +	git branch --list bisecting >actual &&
     +	echo "+ bisecting" >expect &&
     +	test_cmp expect actual
    @@ t/t6302-for-each-ref-filter.sh: test_expect_success 'validate worktree atom' '
     +	git for-each-ref --format="%(worktreepath)" refs/heads/ >actual &&
     +	test_grep ! detached-rebase actual
     +'
    ++
    ++test_expect_success 'worktree atom ignores the HEAD of a bare repository' '
    ++	test_when_finished "rm -rf bare.git bare-wt" &&
    ++	git clone --bare . bare.git &&
    ++	git -C bare.git symbolic-ref HEAD refs/heads/main &&
    ++	git -C bare.git worktree add --detach ../bare-wt main &&
    ++	git -C bare.git for-each-ref --format="%(worktreepath)" refs/heads/main >actual &&
    ++	echo >expect &&
    ++	test_cmp expect actual &&
    ++	git -C bare-wt branch --list main >actual &&
    ++	echo "  main" >expect &&
    ++	test_cmp expect actual
    ++'
     +
      test_expect_success 'start after with empty value' '
      	cat >expect <<-\EOF &&
-:  ---------- > 4:  56e29c448f branch: do not mark the branch the current worktree is rebasing
-- 
2.56.0


  parent reply	other threads:[~2026-11-02 18:29 UTC|newest]

Thread overview: 33+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-10-21 17:33 [RFC PATCH 0/1] worktree list: add --merged and --no-merged Jasper Mayone
2026-10-21 17:33 ` [RFC PATCH 1/1] " Jasper Mayone
2026-10-23 18:30   ` Junio C Hamano
2026-10-24  6:25     ` Eric Sunshine
2026-10-24 16:53       ` [PATCH v2] worktree list: add --branch to select worktrees by branch Jasper Mayone
2026-10-25 14:47         ` Phillip Wood
2026-10-26 11:02           ` Phillip Wood
2026-10-28  6:02           ` Eric Sunshine
2026-10-28 15:27             ` Junio C Hamano
2026-10-28 11:32         ` Kristoffer Haugsbakk
2026-10-29  4:46           ` Jasper Mayone
2026-10-29  5:01             ` Eric Sunshine
2026-10-29 11:14             ` Phillip Wood
2026-10-29 14:23               ` Ben Knoble
2026-10-29 16:10                 ` Phillip Wood
2026-10-30 14:21         ` [PATCH 0/2] worktree: treat a branch being rebased or bisected as checked out Jasper Mayone
2026-10-30 14:21           ` [PATCH 1/2] worktree remove: refuse a worktree in the middle of a rebase or bisect Jasper Mayone
2026-11-02 14:07             ` Phillip Wood
2026-11-02 17:57               ` Jasper Mayone
2026-10-30 14:21           ` [PATCH 2/2] ref-filter: name the worktree of a branch being rebased or bisected Jasper Mayone
2026-11-02 14:06             ` Phillip Wood
2026-11-02 17:57               ` Jasper Mayone
2026-10-30 22:55           ` [PATCH 0/2] worktree: treat a branch being rebased or bisected as checked out Junio C Hamano
2026-10-30 23:06             ` Jasper Mayone
2026-11-02 18:29           ` Jasper Mayone [this message]
2026-11-02 18:29             ` [PATCH v4 1/4] worktree remove: refuse a worktree in the middle of an operation Jasper Mayone
2026-11-02 18:29             ` [PATCH v4 2/4] doc: for-each-ref: say how to read %(worktreepath) safely Jasper Mayone
2026-11-02 18:29             ` [PATCH v4 3/4] ref-filter: name the worktree of a branch being rebased or bisected Jasper Mayone
2026-11-02 18:29             ` [PATCH v4 4/4] branch: do not mark the branch the current worktree is rebasing Jasper Mayone
2026-10-21 21:36 ` [RFC PATCH 0/1] worktree list: add --merged and --no-merged Harald Nordgren
2026-10-21 22:12   ` Junio C Hamano
2026-10-22  7:21     ` Harald Nordgren
2026-10-22 17:58   ` Kristoffer Haugsbakk

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20261102182930.3511-1-me@jaspermayone.com \
    --to=me@jaspermayone.com \
    --cc=ben.knoble@gmail.com \
    --cc=git@vger.kernel.org \
    --cc=gitster@pobox.com \
    --cc=haraldnordgren@gmail.com \
    --cc=kristofferhaugsbakk@fastmail.com \
    --cc=phillip.wood123@gmail.com \
    --cc=sunshine@sunshineco.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox