Skip to content

Commit 17f4b24

Browse files
committed
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 Behavior: From the secondary worktree the main worktree appears as non-bare. 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. Signed-off-by: Olga Pilipenco <[email protected]>
1 parent f93ff17 commit 17f4b24

File tree

2 files changed

+43
-9
lines changed

2 files changed

+43
-9
lines changed

t/t3200-branch.sh

+14
Original file line numberDiff line numberDiff line change
@@ -410,6 +410,20 @@ test_expect_success 'bare main worktree has HEAD at branch deleted by secondary
410410
git -C secondary branch -D main
411411
'
412412

413+
test_expect_success 'secondary worktree can switch to main if common dir is bare worktree' '
414+
test_when_finished "rm -rf bare_repo non_bare_repo secondary_worktree" &&
415+
git init -b main non_bare_repo &&
416+
test_commit -C non_bare_repo x &&
417+
418+
git clone --bare non_bare_repo bare_repo &&
419+
git -C bare_repo config extensions.worktreeConfig true &&
420+
git -C bare_repo config unset core.bare &&
421+
git -C bare_repo config --worktree core.bare true &&
422+
423+
git -C bare_repo worktree add ../secondary_worktree &&
424+
git -C secondary_worktree checkout main
425+
'
426+
413427
test_expect_success 'git branch --list -v with --abbrev' '
414428
test_when_finished "git branch -D t" &&
415429
git branch t &&

worktree.c

+29-9
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,28 @@ static int is_current_worktree(struct worktree *wt)
6565
return is_current;
6666
}
6767

68+
static int is_bare_git_dir(const char *git_dir)
69+
{
70+
int bare = 0;
71+
struct config_set cs = { { 0 } };
72+
char *config_file;
73+
char *worktree_config_file;
74+
75+
config_file = xstrfmt("%s/config", git_dir);
76+
worktree_config_file = xstrfmt("%s/config.worktree", git_dir);
77+
78+
git_configset_init(&cs);
79+
git_configset_add_file(&cs, config_file);
80+
git_configset_add_file(&cs, worktree_config_file);
81+
82+
git_configset_get_bool(&cs, "core.bare", &bare);
83+
84+
git_configset_clear(&cs);
85+
free(config_file);
86+
free(worktree_config_file);
87+
return bare;
88+
}
89+
6890
/**
6991
* get the main worktree
7092
*/
@@ -77,18 +99,16 @@ static struct worktree *get_main_worktree(int skip_reading_head)
7799
strbuf_strip_suffix(&worktree_path, "/.git");
78100

79101
CALLOC_ARRAY(worktree, 1);
102+
/*
103+
* NEEDSWORK: the_repository is not always main worktree's repository
104+
*/
80105
worktree->repo = the_repository;
81106
worktree->path = strbuf_detach(&worktree_path, NULL);
82-
/*
83-
* NEEDSWORK: If this function is called from a secondary worktree and
84-
* config.worktree is present, is_bare_repository_cfg will reflect the
85-
* contents of config.worktree, not the contents of the main worktree.
86-
* This means that worktree->is_bare may be set to 0 even if the main
87-
* worktree is configured to be bare.
88-
*/
89-
worktree->is_bare = (is_bare_repository_cfg == 1) ||
90-
is_bare_repository();
91107
worktree->is_current = is_current_worktree(worktree);
108+
worktree->is_bare = (is_bare_repository_cfg == 1) ||
109+
is_bare_repository() ||
110+
(!worktree->is_current && is_bare_git_dir(repo_get_common_dir(the_repository)));
111+
92112
if (!skip_reading_head)
93113
add_head_info(worktree);
94114
return worktree;

0 commit comments

Comments
 (0)