diff mbox series

[v3] worktree: detect from secondary worktree if main worktree is bare

Message ID pull.1829.v3.git.1738346881907.gitgitgadget@gmail.com (mailing list archive)
State Superseded
Headers show
Series [v3] worktree: detect from secondary worktree if main worktree is bare | expand

Commit Message

Olga Pilipenco Jan. 31, 2025, 6:08 p.m. UTC
From: Olga Pilipenco <olga.pilipenco@shopify.com>

When extensions.worktreeConfig is true and the main worktree is
bare -- that is, its config.worktree file contains core.bare=true
-- commands run from secondary worktrees incorrectly see the main
worktree as not bare. As such, those commands incorrectly think
that the repository's default branch (typically "main" or
"master") is checked out in the bare repository even though it's
not. This makes it impossible, for instance, to checkout or delete
the default branch from a secondary worktree, among other
shortcomings.

This problem occurs because, when extensions.worktreeConfig is
true, commands run in secondary worktrees only consult
$commondir/config and $commondir/worktrees/<id>/config.worktree,
thus they never see the main worktree's core.bare=true setting in
$commondir/config.worktree.

Fix this problem by consulting the main worktree's config.worktree
file when checking whether it is bare. (This extra work is
performed only when running from a secondary worktree.)

Helped-by: Eric Sunshine <sunshine@sunshineco.com>
Signed-off-by: Olga Pilipenco <olga.pilipenco@shopify.com>
---
    worktree: detect from secondary worktree if main worktree is bare
    
    Changes since v2, all results of the amazing review:
    
     * updated description & comments;
     * private function is_bare_git_dir is replaced with
       is_main_worktree_bare. The new implementation only checks if main
       worktree's worktree.config contains information if main worktree is
       bare or not. It's assumed that other configs of main worktree are
       already checked for bareness prior this call.
     * notation { { 0 } } is replaced with {0} that is preferred by the
       project.

Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1829%2Folga-mcbfe%2Ffix-bare-repo-detection-with-worktree-config-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1829/olga-mcbfe/fix-bare-repo-detection-with-worktree-config-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1829

Range-diff vs v2:

 1:  17f4b24d1da ! 1:  f9207746b38 worktree: detect from secondary worktree if main worktree is bare
     @@ Metadata
       ## Commit message ##
          worktree: detect from secondary worktree if main worktree is bare
      
     -    Setup:
     -    1. Have a bare repo with core.bare = true in config.worktree
     -    2. Create a new worktree
     +    When extensions.worktreeConfig is true and the main worktree is
     +    bare -- that is, its config.worktree file contains core.bare=true
     +    -- commands run from secondary worktrees incorrectly see the main
     +    worktree as not bare. As such, those commands incorrectly think
     +    that the repository's default branch (typically "main" or
     +    "master") is checked out in the bare repository even though it's
     +    not. This makes it impossible, for instance, to checkout or delete
     +    the default branch from a secondary worktree, among other
     +    shortcomings.
      
     -    Behavior:
     -    From the secondary worktree the main worktree appears as non-bare.
     +    This problem occurs because, when extensions.worktreeConfig is
     +    true, commands run in secondary worktrees only consult
     +    $commondir/config and $commondir/worktrees/<id>/config.worktree,
     +    thus they never see the main worktree's core.bare=true setting in
     +    $commondir/config.worktree.
      
     -    Expected:
     -    From the secondary worktree the main worktree should appear as bare.
     -
     -    Why current behavior is not good?
     -    If the main worktree is detected as not bare it doesn't allow
     -    checking out the branch of the main worktree. There are possibly
     -    other problems associated with that behavior.
     -
     -    Why is it happening?
     -    While we're inside the secondary worktree we don't initialize the main
     -    worktree's repository with its configuration.
     -
     -    How is it fixed?
     -    Load actual configs of the main worktree. Also, skip the config loading
     -    step if we're already inside the current worktree because in that case we
     -    rely on is_bare_repository() to return the correct result.
     -
     -    Other solutions considered:
     -    Alternatively, instead of incorrectly always using
     -    `the_repository` as the main worktree's repository, we can detect
     -    and load the actual repository of the main worktree and then use
     -    that repository's `is_bare` value extracted from correct configs.
     -    However, this approach is a bit riskier and could also affect
     -    performance. Since we had the assignment `worktree->repo =
     -    the_repository` for a long time already, I decided it's safe to
     -    keep it as it is for now; it can be still fixed separately from
     -    this change.
     -
     -    Real life use case:
     -    1. Have a bare repo
     -    2. Create a worktree from the bare repo
     -    3. In the secondary worktree enable sparse-checkout - this enables
     -    extensions.worktreeConfig and keeps core.bare=true setting in
     -    config.worktree of the bare worktree
     -    4. The secondary worktree or any other non-bare worktree created
     -    won't be able to use branch main (not even once), but it should be
     -    able to.
     +    Fix this problem by consulting the main worktree's config.worktree
     +    file when checking whether it is bare. (This extra work is
     +    performed only when running from a secondary worktree.)
      
     +    Helped-by: Eric Sunshine <sunshine@sunshineco.com>
          Signed-off-by: Olga Pilipenco <olga.pilipenco@shopify.com>
      
       ## t/t3200-branch.sh ##
     @@ t/t3200-branch.sh: test_expect_success 'bare main worktree has HEAD at branch de
       	git -C secondary branch -D main
       '
       
     -+test_expect_success 'secondary worktree can switch to main if common dir is bare worktree' '
     ++test_expect_success 'secondary worktrees recognize core.bare=true in main config.worktree' '
      +	test_when_finished "rm -rf bare_repo non_bare_repo secondary_worktree" &&
      +	git init -b main non_bare_repo &&
      +	test_commit -C non_bare_repo x &&
     @@ worktree.c: static int is_current_worktree(struct worktree *wt)
       	return is_current;
       }
       
     -+static int is_bare_git_dir(const char *git_dir)
     ++/*
     ++* When in a secondary worktree, and when extensions.worktreeConfig
     ++* is true, only $commondir/config and $commondir/worktrees/<id>/
     ++* config.worktree are consulted, hence any core.bare=true setting in
     ++* $commondir/config.worktree gets overlooked. Thus, check it manually
     ++* to determine if the repository is bare.
     ++*/
     ++static int is_main_worktree_bare(struct repository *repo)
      +{
      +	int bare = 0;
     -+	struct config_set cs = { { 0 } };
     -+	char *config_file;
     -+	char *worktree_config_file;
     -+
     -+	config_file = xstrfmt("%s/config", git_dir);
     -+	worktree_config_file = xstrfmt("%s/config.worktree",  git_dir);
     ++	struct config_set cs = {0};
     ++	char *worktree_config = xstrfmt("%s/config.worktree", repo_get_common_dir(repo));
      +
      +	git_configset_init(&cs);
     -+	git_configset_add_file(&cs, config_file);
     -+	git_configset_add_file(&cs, worktree_config_file);
     -+
     ++	git_configset_add_file(&cs, worktree_config);
      +	git_configset_get_bool(&cs, "core.bare", &bare);
      +
      +	git_configset_clear(&cs);
     -+	free(config_file);
     -+	free(worktree_config_file);
     ++	free(worktree_config);
      +	return bare;
      +}
      +
     @@ worktree.c: static int is_current_worktree(struct worktree *wt)
        * get the main worktree
        */
      @@ worktree.c: static struct worktree *get_main_worktree(int skip_reading_head)
     - 	strbuf_strip_suffix(&worktree_path, "/.git");
     - 
       	CALLOC_ARRAY(worktree, 1);
     -+	/*
     -+	 * NEEDSWORK: the_repository is not always main worktree's repository
     -+	*/
       	worktree->repo = the_repository;
       	worktree->path = strbuf_detach(&worktree_path, NULL);
      -	/*
     @@ worktree.c: static struct worktree *get_main_worktree(int skip_reading_head)
       	worktree->is_current = is_current_worktree(worktree);
      +	worktree->is_bare = (is_bare_repository_cfg == 1) ||
      +		is_bare_repository() ||
     -+		(!worktree->is_current && is_bare_git_dir(repo_get_common_dir(the_repository)));
     ++		(!worktree->is_current && is_main_worktree_bare(the_repository));
      +
       	if (!skip_reading_head)
       		add_head_info(worktree);


 t/t3200-branch.sh | 14 ++++++++++++++
 worktree.c        | 35 ++++++++++++++++++++++++++---------
 2 files changed, 40 insertions(+), 9 deletions(-)


base-commit: f93ff170b93a1782659637824b25923245ac9dd1

Comments

Junio C Hamano Jan. 31, 2025, 7:19 p.m. UTC | #1
"Olga Pilipenco via GitGitGadget" <gitgitgadget@gmail.com> writes:

> +/*
> +* When in a secondary worktree, and when extensions.worktreeConfig
> +* is true, only $commondir/config and $commondir/worktrees/<id>/
> +* config.worktree are consulted, hence any core.bare=true setting in
> +* $commondir/config.worktree gets overlooked. Thus, check it manually
> +* to determine if the repository is bare.
> +*/
> +static int is_main_worktree_bare(struct repository *repo)
> +{
> +	int bare = 0;
> +	struct config_set cs = {0};
> +	char *worktree_config = xstrfmt("%s/config.worktree", repo_get_common_dir(repo));
> +
> +	git_configset_init(&cs);
> +	git_configset_add_file(&cs, worktree_config);
> +	git_configset_get_bool(&cs, "core.bare", &bare);
> +
> +	git_configset_clear(&cs);
> +	free(worktree_config);
> +	return bare;
> +}

That is nicely described.

>  /**
>   * get the main worktree
>   */
> @@ -79,16 +101,11 @@ static struct worktree *get_main_worktree(int skip_reading_head)
>  	CALLOC_ARRAY(worktree, 1);
>  	worktree->repo = the_repository;
>  	worktree->path = strbuf_detach(&worktree_path, NULL);
> -	/*
> -	 * NEEDSWORK: If this function is called from a secondary worktree and
> -	 * config.worktree is present, is_bare_repository_cfg will reflect the
> -	 * contents of config.worktree, not the contents of the main worktree.
> -	 * This means that worktree->is_bare may be set to 0 even if the main
> -	 * worktree is configured to be bare.
> -	 */
> -	worktree->is_bare = (is_bare_repository_cfg == 1) ||
> -		is_bare_repository();
>  	worktree->is_current = is_current_worktree(worktree);
> +	worktree->is_bare = (is_bare_repository_cfg == 1) ||
> +		is_bare_repository() ||
> +		(!worktree->is_current && is_main_worktree_bare(the_repository));

Is "this worktree does not have is_current bit set" equivalent to
"this worktree is the main one, so is_main_worktree_bare() needs to
be consulted"?  That linkage between "the is_current bit unset" and
"is the main worktree" is not obvious to me.

Thanks.
Junio C Hamano Jan. 31, 2025, 7:26 p.m. UTC | #2
Junio C Hamano <gitster@pobox.com> writes:

> "Olga Pilipenco via GitGitGadget" <gitgitgadget@gmail.com> writes:
>
>> +/*
>> +* When in a secondary worktree, and when extensions.worktreeConfig
>> +* is true, only $commondir/config and $commondir/worktrees/<id>/
>> +* config.worktree are consulted, hence any core.bare=true setting in
>> +* $commondir/config.worktree gets overlooked. Thus, check it manually
>> +* to determine if the repository is bare.
>> +*/
>> +static int is_main_worktree_bare(struct repository *repo)
>> +{
>> +	int bare = 0;
>> +	struct config_set cs = {0};
>> +	char *worktree_config = xstrfmt("%s/config.worktree", repo_get_common_dir(repo));
>> +
>> +	git_configset_init(&cs);
>> +	git_configset_add_file(&cs, worktree_config);
>> +	git_configset_get_bool(&cs, "core.bare", &bare);
>> +
>> +	git_configset_clear(&cs);
>> +	free(worktree_config);
>> +	return bare;
>> +}
>
> That is nicely described.
>
>>  /**
>>   * get the main worktree
>>   */
>> @@ -79,16 +101,11 @@ static struct worktree *get_main_worktree(int skip_reading_head)
>>  	CALLOC_ARRAY(worktree, 1);
>>  	worktree->repo = the_repository;
>>  	worktree->path = strbuf_detach(&worktree_path, NULL);
>> -	/*
>> -	 * NEEDSWORK: If this function is called from a secondary worktree and
>> -	 * config.worktree is present, is_bare_repository_cfg will reflect the
>> -	 * contents of config.worktree, not the contents of the main worktree.
>> -	 * This means that worktree->is_bare may be set to 0 even if the main
>> -	 * worktree is configured to be bare.
>> -	 */
>> -	worktree->is_bare = (is_bare_repository_cfg == 1) ||
>> -		is_bare_repository();
>>  	worktree->is_current = is_current_worktree(worktree);
>> +	worktree->is_bare = (is_bare_repository_cfg == 1) ||
>> +		is_bare_repository() ||
>> +		(!worktree->is_current && is_main_worktree_bare(the_repository));
>
> Is "this worktree does not have is_current bit set" equivalent to
> "this worktree is the main one, so is_main_worktree_bare() needs to
> be consulted"?  That linkage between "the is_current bit unset" and
> "is the main worktree" is not obvious to me.

Does the thinking behind it go like this?

    We grabbed the "main" worktree object and stored it in worktree;
    it is either our current worktree (in which case is_current is
    true), or it is not (in which case, is_current is false).  We
    know that the old logic failed when asking the "is it bare"
    question from a secondary worktree.  !worktree->is_current tells
    us that we _are_ asking the question from a secondary worktree,
    so we need to make the extra call to check config.worktree file
    as well in that case.

Perhaps the logic is clear to those who diagnosed the problem, wrote
the patch, and reviewed it, in which case there is no reason to
reroll.  Perhaps it was just me to whom it was not obvious that
the purpose of "is_current" check was not about "are we looking at
the main worktree" but was about "if we are not in the main worktree,
we need this extra check".

Thanks.
Olga Pilipenco Jan. 31, 2025, 8:11 p.m. UTC | #3
On Fri, Jan 31, 2025 at 12:26 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Junio C Hamano <gitster@pobox.com> writes:
>
> > "Olga Pilipenco via GitGitGadget" <gitgitgadget@gmail.com> writes:
> >
> >> +/*
> >> +* When in a secondary worktree, and when extensions.worktreeConfig
> >> +* is true, only $commondir/config and $commondir/worktrees/<id>/
> >> +* config.worktree are consulted, hence any core.bare=true setting in
> >> +* $commondir/config.worktree gets overlooked. Thus, check it manually
> >> +* to determine if the repository is bare.
> >> +*/
> >> +static int is_main_worktree_bare(struct repository *repo)
> >> +{
> >> +    int bare = 0;
> >> +    struct config_set cs = {0};
> >> +    char *worktree_config = xstrfmt("%s/config.worktree", repo_get_common_dir(repo));
> >> +
> >> +    git_configset_init(&cs);
> >> +    git_configset_add_file(&cs, worktree_config);
> >> +    git_configset_get_bool(&cs, "core.bare", &bare);
> >> +
> >> +    git_configset_clear(&cs);
> >> +    free(worktree_config);
> >> +    return bare;
> >> +}
> >
> > That is nicely described.
> >
> >>  /**
> >>   * get the main worktree
> >>   */
> >> @@ -79,16 +101,11 @@ static struct worktree *get_main_worktree(int skip_reading_head)
> >>      CALLOC_ARRAY(worktree, 1);
> >>      worktree->repo = the_repository;
> >>      worktree->path = strbuf_detach(&worktree_path, NULL);
> >> -    /*
> >> -     * NEEDSWORK: If this function is called from a secondary worktree and
> >> -     * config.worktree is present, is_bare_repository_cfg will reflect the
> >> -     * contents of config.worktree, not the contents of the main worktree.
> >> -     * This means that worktree->is_bare may be set to 0 even if the main
> >> -     * worktree is configured to be bare.
> >> -     */
> >> -    worktree->is_bare = (is_bare_repository_cfg == 1) ||
> >> -            is_bare_repository();
> >>      worktree->is_current = is_current_worktree(worktree);
> >> +    worktree->is_bare = (is_bare_repository_cfg == 1) ||
> >> +            is_bare_repository() ||
> >> +            (!worktree->is_current && is_main_worktree_bare(the_repository));
> >
> > Is "this worktree does not have is_current bit set" equivalent to
> > "this worktree is the main one, so is_main_worktree_bare() needs to
> > be consulted"?  That linkage between "the is_current bit unset" and
> > "is the main worktree" is not obvious to me.
>
> Does the thinking behind it go like this?
>
>     We grabbed the "main" worktree object and stored it in worktree;
>     it is either our current worktree (in which case is_current is
>     true), or it is not (in which case, is_current is false).  We
>     know that the old logic failed when asking the "is it bare"
>     question from a secondary worktree.  !worktree->is_current tells
>     us that we _are_ asking the question from a secondary worktree,
>     so we need to make the extra call to check config.worktree file
>     as well in that case.
>
> Perhaps the logic is clear to those who diagnosed the problem, wrote
> the patch, and reviewed it, in which case there is no reason to
> reroll.  Perhaps it was just me to whom it was not obvious that
> the purpose of "is_current" check was not about "are we looking at
> the main worktree" but was about "if we are not in the main worktree,
> we need this extra check".
>
> Thanks.

You did a great job figuring it out and I agree it's confusing at
first, but we tried
our best to make it less confusing.
`is_current` check is actually not necessary there, but having it there saves
extra unnecessary calculations, also describes & fixes the exact scenario
that didn't work (not being able to see main worktree as bare from a
secondary worktree).

Thanks for looking into that.
Junio C Hamano Jan. 31, 2025, 8:20 p.m. UTC | #4
Olga Pilipenco <olga.pilipenco@shopify.com> writes:

>> Perhaps the logic is clear to those who diagnosed the problem, wrote
>> the patch, and reviewed it, in which case there is no reason to
>> reroll.  Perhaps it was just me to whom it was not obvious that
>> the purpose of "is_current" check was not about "are we looking at
>> the main worktree" but was about "if we are not in the main worktree,
>> we need this extra check".
>>
>> Thanks.
>
> You did a great job figuring it out and I agree it's confusing at
> first, but we tried our best to make it less confusing.
> `is_current` check is actually not necessary there, but having it there saves
> extra unnecessary calculations, also describes & fixes the exact scenario
> that didn't work (not being able to see main worktree as bare from a
> secondary worktree).

If I had to do a great job there, then the code does deserve to be
explained a bit better for later developers who wonder why it is
written in the way it is, perhaps we a single-liner comment?

Thanks.
Olga Pilipenco Feb. 4, 2025, 7:03 p.m. UTC | #5
On Fri, Jan 31, 2025 at 1:20 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Olga Pilipenco <olga.pilipenco@shopify.com> writes:
>
> >> Perhaps the logic is clear to those who diagnosed the problem, wrote
> >> the patch, and reviewed it, in which case there is no reason to
> >> reroll.  Perhaps it was just me to whom it was not obvious that
> >> the purpose of "is_current" check was not about "are we looking at
> >> the main worktree" but was about "if we are not in the main worktree,
> >> we need this extra check".
> >>
> >> Thanks.
> >
> > You did a great job figuring it out and I agree it's confusing at
> > first, but we tried our best to make it less confusing.
> > `is_current` check is actually not necessary there, but having it there saves
> > extra unnecessary calculations, also describes & fixes the exact scenario
> > that didn't work (not being able to see main worktree as bare from a
> > secondary worktree).
>
> If I had to do a great job there, then the code does deserve to be
> explained a bit better for later developers who wonder why it is
> written in the way it is, perhaps we a single-liner comment?

I have 2 versions for comment:

1. Since is_main_worktree_bare explains quite well what it does we can have
a shorter explanation of `!worktree->is_current` part, something like:

/* Additional checks are needed if main worktree is not current
(checking from secondary worktree) */
(!worktree->is_current && is_main_worktree_bare(the_repository));

2. Or a bit longer inline explanation that partially repeats the
explanation of is_main_worktree_bare
+ adds explanation about efficiency:
 /*
  * When in a secondary worktree we have to also verify if the main worktree
  * is bare in $commondir/config.worktree.
  * This check is unnecessary if we're currently in the main worktree,
  * as prior checks already consulted all configs of the current worktree.
 */
(!worktree->is_current && is_main_worktree_bare(the_repository));

Let me know if any of these work. Thanks.

> Thanks.
Junio C Hamano Feb. 4, 2025, 7:43 p.m. UTC | #6
Olga Pilipenco <olga.pilipenco@shopify.com> writes:

> I have 2 versions for comment:
>
> 1. Since is_main_worktree_bare explains quite well what it does we can have
> a shorter explanation of `!worktree->is_current` part, something like:
>
> /* Additional checks are needed if main worktree is not current
> (checking from secondary worktree) */
> (!worktree->is_current && is_main_worktree_bare(the_repository));

For somebody who thought about the issue themselves (like me, before
writing the message you are responding to), this shorter form would
suffice.  I'd rephrase it more like so

    /* When a secondary worktree, an extra check is needed */

for brevity, though.


> 2. Or a bit longer inline explanation that partially repeats the
> explanation of is_main_worktree_bare
> + adds explanation about efficiency:
>  /*
>   * When in a secondary worktree we have to also verify if the main worktree
>   * is bare in $commondir/config.worktree.
>   * This check is unnecessary if we're currently in the main worktree,
>   * as prior checks already consulted all configs of the current worktree.
>  */
> (!worktree->is_current && is_main_worktree_bare(the_repository));

And this more extended version would have helped me by not having to
ask

    Is "this worktree does not have is_current bit set" equivalent
    to "this worktree is the main one, so is_main_worktree_bare()
    needs to be consulted"?  That linkage between "the is_current
    bit unset" and "is the main worktree" is not obvious to me.

in the first place.

In short, both should work, and I personally find that the latter
may be a bit more helpful to readers.

THanks.
Olga Pilipenco Feb. 4, 2025, 8:33 p.m. UTC | #7
On Tue, Feb 4, 2025 at 12:43 PM Junio C Hamano <gitster@pobox.com> wrote:
>
> Olga Pilipenco <olga.pilipenco@shopify.com> writes:
>
> > I have 2 versions for comment:
> >
> > 1. Since is_main_worktree_bare explains quite well what it does we can have
> > a shorter explanation of `!worktree->is_current` part, something like:
> >
> > /* Additional checks are needed if main worktree is not current
> > (checking from secondary worktree) */
> > (!worktree->is_current && is_main_worktree_bare(the_repository));
>
> For somebody who thought about the issue themselves (like me, before
> writing the message you are responding to), this shorter form would
> suffice.  I'd rephrase it more like so
>
>     /* When a secondary worktree, an extra check is needed */
>
> for brevity, though.
>
>
> > 2. Or a bit longer inline explanation that partially repeats the
> > explanation of is_main_worktree_bare
> > + adds explanation about efficiency:
> >  /*
> >   * When in a secondary worktree we have to also verify if the main worktree
> >   * is bare in $commondir/config.worktree.
> >   * This check is unnecessary if we're currently in the main worktree,
> >   * as prior checks already consulted all configs of the current worktree.
> >  */
> > (!worktree->is_current && is_main_worktree_bare(the_repository));
>
> And this more extended version would have helped me by not having to
> ask
>
>     Is "this worktree does not have is_current bit set" equivalent
>     to "this worktree is the main one, so is_main_worktree_bare()
>     needs to be consulted"?  That linkage between "the is_current
>     bit unset" and "is the main worktree" is not obvious to me.
>
> in the first place.
>
> In short, both should work, and I personally find that the latter
> may be a bit more helpful to readers.
>
> THanks.

Perfect, I'll add the latter one then. Thank you!
diff mbox series

Patch

diff --git a/t/t3200-branch.sh b/t/t3200-branch.sh
index a3a21c54cf6..f3e720dc10d 100755
--- a/t/t3200-branch.sh
+++ b/t/t3200-branch.sh
@@ -410,6 +410,20 @@  test_expect_success 'bare main worktree has HEAD at branch deleted by secondary
 	git -C secondary branch -D main
 '
 
+test_expect_success 'secondary worktrees recognize core.bare=true in main config.worktree' '
+	test_when_finished "rm -rf bare_repo non_bare_repo secondary_worktree" &&
+	git init -b main non_bare_repo &&
+	test_commit -C non_bare_repo x &&
+
+	git clone --bare non_bare_repo bare_repo &&
+	git -C bare_repo config extensions.worktreeConfig true &&
+	git -C bare_repo config unset core.bare &&
+	git -C bare_repo config --worktree core.bare true &&
+
+	git -C bare_repo worktree add ../secondary_worktree &&
+	git -C secondary_worktree checkout main
+'
+
 test_expect_success 'git branch --list -v with --abbrev' '
 	test_when_finished "git branch -D t" &&
 	git branch t &&
diff --git a/worktree.c b/worktree.c
index 248bbb39d43..6df4ccf97f7 100644
--- a/worktree.c
+++ b/worktree.c
@@ -65,6 +65,28 @@  static int is_current_worktree(struct worktree *wt)
 	return is_current;
 }
 
+/*
+* When in a secondary worktree, and when extensions.worktreeConfig
+* is true, only $commondir/config and $commondir/worktrees/<id>/
+* config.worktree are consulted, hence any core.bare=true setting in
+* $commondir/config.worktree gets overlooked. Thus, check it manually
+* to determine if the repository is bare.
+*/
+static int is_main_worktree_bare(struct repository *repo)
+{
+	int bare = 0;
+	struct config_set cs = {0};
+	char *worktree_config = xstrfmt("%s/config.worktree", repo_get_common_dir(repo));
+
+	git_configset_init(&cs);
+	git_configset_add_file(&cs, worktree_config);
+	git_configset_get_bool(&cs, "core.bare", &bare);
+
+	git_configset_clear(&cs);
+	free(worktree_config);
+	return bare;
+}
+
 /**
  * get the main worktree
  */
@@ -79,16 +101,11 @@  static struct worktree *get_main_worktree(int skip_reading_head)
 	CALLOC_ARRAY(worktree, 1);
 	worktree->repo = the_repository;
 	worktree->path = strbuf_detach(&worktree_path, NULL);
-	/*
-	 * NEEDSWORK: If this function is called from a secondary worktree and
-	 * config.worktree is present, is_bare_repository_cfg will reflect the
-	 * contents of config.worktree, not the contents of the main worktree.
-	 * This means that worktree->is_bare may be set to 0 even if the main
-	 * worktree is configured to be bare.
-	 */
-	worktree->is_bare = (is_bare_repository_cfg == 1) ||
-		is_bare_repository();
 	worktree->is_current = is_current_worktree(worktree);
+	worktree->is_bare = (is_bare_repository_cfg == 1) ||
+		is_bare_repository() ||
+		(!worktree->is_current && is_main_worktree_bare(the_repository));
+
 	if (!skip_reading_head)
 		add_head_info(worktree);
 	return worktree;