Skip to content

Commit 39abd3a

Browse files
committed
worktree: compute workdir for worktrees opened via their gitdir
When opening a worktree via the gitdir of its parent repository we fail to correctly set up the worktree's working directory. The problem here is two-fold: we first fail to see that the gitdir actually is a gitdir of a working tree and then subsequently fail to determine the working tree location from the gitdir. The first problem of not noticing a gitdir belongs to a worktree can be solved by checking for the existence of a `gitdir` file in the gitdir. This file points back to the gitlink file located in the working tree's working directory. As this file only exists for worktrees, it should be sufficient indication of the gitdir belonging to a worktree. The second problem, that is determining the location of the worktree's working directory, can then be solved by reading the `gitdir` file in the working directory's gitdir. When we now resolve relative paths and strip the final `.git` component, we have the actual worktree's working directory location.
1 parent 84f56cb commit 39abd3a

File tree

5 files changed

+66
-12
lines changed

5 files changed

+66
-12
lines changed

src/repository.c

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ static const struct {
6161
static int check_repositoryformatversion(git_config *config);
6262

6363
#define GIT_COMMONDIR_FILE "commondir"
64+
#define GIT_GITDIR_FILE "gitdir"
6465

6566
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
6667

@@ -275,9 +276,10 @@ static int load_config_data(git_repository *repo, const git_config *config)
275276

276277
static int load_workdir(git_repository *repo, git_config *config, git_buf *parent_path)
277278
{
278-
int error;
279+
int error;
279280
git_config_entry *ce;
280-
git_buf worktree = GIT_BUF_INIT;
281+
git_buf worktree = GIT_BUF_INIT;
282+
git_buf path = GIT_BUF_INIT;
281283

282284
if (repo->is_bare)
283285
return 0;
@@ -286,7 +288,24 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
286288
&ce, config, "core.worktree", false)) < 0)
287289
return error;
288290

289-
if (ce && ce->value) {
291+
if (repo->is_worktree) {
292+
char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE);
293+
if (!gitlink) {
294+
error = -1;
295+
goto cleanup;
296+
}
297+
298+
git_buf_attach(&worktree, gitlink, 0);
299+
300+
if ((git_path_dirname_r(&worktree, worktree.ptr)) < 0 ||
301+
git_path_to_dir(&worktree) < 0) {
302+
error = -1;
303+
goto cleanup;
304+
}
305+
306+
repo->workdir = git_buf_detach(&worktree);
307+
}
308+
else if (ce && ce->value) {
290309
if ((error = git_path_prettify_dir(
291310
&worktree, ce->value, repo->gitdir)) < 0)
292311
goto cleanup;
@@ -307,6 +326,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
307326

308327
GITERR_CHECK_ALLOC(repo->workdir);
309328
cleanup:
329+
git_buf_free(&path);
310330
git_config_entry_free(ce);
311331
return error;
312332
}
@@ -465,6 +485,9 @@ static int find_repo(
465485
git_path_to_dir(&path);
466486
git_buf_set(repo_path, path.ptr, path.size);
467487

488+
if (link_path)
489+
git_buf_attach(link_path,
490+
git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0);
468491
if (common_path)
469492
git_buf_swap(&common_link, common_path);
470493

@@ -775,7 +798,11 @@ int git_repository_open_ext(
775798
GITERR_CHECK_ALLOC(repo->commondir);
776799
}
777800

778-
if (repo->gitlink && repo->commondir && strcmp(repo->gitlink, repo->commondir))
801+
if ((error = git_buf_joinpath(&path, repo->gitdir, "gitdir")) < 0)
802+
goto cleanup;
803+
/* A 'gitdir' file inside a git directory is currently
804+
* only used when the repository is a working tree. */
805+
if (git_path_exists(path.ptr))
779806
repo->is_worktree = 1;
780807

781808
/*
@@ -801,6 +828,7 @@ int git_repository_open_ext(
801828
}
802829

803830
cleanup:
831+
git_buf_free(&path);
804832
git_buf_free(&parent);
805833
git_config_free(config);
806834

src/worktree.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ int git_worktree_list(git_strarray *wts, git_repository *repo)
6161
return error;
6262
}
6363

64-
static char *read_link(const char *base, const char *file)
64+
char *git_worktree__read_link(const char *base, const char *file)
6565
{
6666
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
6767

@@ -136,8 +136,8 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na
136136
}
137137

138138
if ((wt->name = git__strdup(name)) == NULL
139-
|| (wt->commondir_path = read_link(path.ptr, "commondir")) == NULL
140-
|| (wt->gitlink_path = read_link(path.ptr, "gitdir")) == NULL
139+
|| (wt->commondir_path = git_worktree__read_link(path.ptr, "commondir")) == NULL
140+
|| (wt->gitlink_path = git_worktree__read_link(path.ptr, "gitdir")) == NULL
141141
|| (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) {
142142
error = -1;
143143
goto out;

src/worktree.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,4 +30,6 @@ struct git_worktree {
3030
int locked:1;
3131
};
3232

33+
char *git_worktree__read_link(const char *base, const char *file);
34+
3335
#endif

tests/worktree/open.c

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,13 @@
55
#define WORKTREE_PARENT "submodules-worktree-parent"
66
#define WORKTREE_CHILD "submodules-worktree-child"
77

8+
#define COMMON_REPO "testrepo"
9+
#define WORKTREE_REPO "testrepo-worktree"
10+
811
void test_worktree_open__repository(void)
912
{
1013
worktree_fixture fixture =
11-
WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree");
14+
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
1215
setup_fixture_worktree(&fixture);
1316

1417
cl_assert(git_repository_path(fixture.worktree) != NULL);
@@ -20,18 +23,38 @@ void test_worktree_open__repository(void)
2023
cleanup_fixture_worktree(&fixture);
2124
}
2225

26+
void test_worktree_open__open_discovered_worktree(void)
27+
{
28+
worktree_fixture fixture =
29+
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
30+
git_buf path = GIT_BUF_INIT;
31+
git_repository *repo;
32+
33+
setup_fixture_worktree(&fixture);
34+
35+
cl_git_pass(git_repository_discover(&path,
36+
git_repository_workdir(fixture.worktree), false, NULL));
37+
cl_git_pass(git_repository_open(&repo, path.ptr));
38+
cl_assert_equal_s(git_repository_workdir(fixture.worktree),
39+
git_repository_workdir(repo));
40+
41+
git_buf_free(&path);
42+
git_repository_free(repo);
43+
cleanup_fixture_worktree(&fixture);
44+
}
45+
2346
void test_worktree_open__repository_with_nonexistent_parent(void)
2447
{
2548
git_repository *repo;
2649

27-
cl_fixture_sandbox("testrepo-worktree");
28-
cl_git_pass(p_chdir("testrepo-worktree"));
50+
cl_fixture_sandbox(WORKTREE_REPO);
51+
cl_git_pass(p_chdir(WORKTREE_REPO));
2952
cl_git_pass(cl_rename(".gitted", ".git"));
3053
cl_git_pass(p_chdir(".."));
3154

32-
cl_git_fail(git_repository_open(&repo, "testrepo-worktree"));
55+
cl_git_fail(git_repository_open(&repo, WORKTREE_REPO));
3356

34-
cl_fixture_cleanup("testrepo-worktree");
57+
cl_fixture_cleanup(WORKTREE_REPO);
3558
}
3659

3760
void test_worktree_open__submodule_worktree_parent(void)

tests/worktree/worktree.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,7 @@ void test_worktree_worktree__init(void)
217217

218218
/* Open and verify created repo */
219219
cl_git_pass(git_repository_open(&repo, path.ptr));
220+
cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-new/") == 0);
220221
cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL));
221222

222223
git_buf_free(&path);

0 commit comments

Comments
 (0)