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 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199
diff --git a/src/repository.c b/src/repository.c
index 5572484..4b937be 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -61,6 +61,7 @@ static const struct {
static int check_repositoryformatversion(git_config *config);
#define GIT_COMMONDIR_FILE "commondir"
+#define GIT_GITDIR_FILE "gitdir"
#define GIT_FILE_CONTENT_PREFIX "gitdir:"
@@ -275,9 +276,10 @@ static int load_config_data(git_repository *repo, const git_config *config)
static int load_workdir(git_repository *repo, git_config *config, git_buf *parent_path)
{
- int error;
+ int error;
git_config_entry *ce;
- git_buf worktree = GIT_BUF_INIT;
+ git_buf worktree = GIT_BUF_INIT;
+ git_buf path = GIT_BUF_INIT;
if (repo->is_bare)
return 0;
@@ -286,7 +288,24 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
&ce, config, "core.worktree", false)) < 0)
return error;
- if (ce && ce->value) {
+ if (repo->is_worktree) {
+ char *gitlink = git_worktree__read_link(repo->gitdir, GIT_GITDIR_FILE);
+ if (!gitlink) {
+ error = -1;
+ goto cleanup;
+ }
+
+ git_buf_attach(&worktree, gitlink, 0);
+
+ if ((git_path_dirname_r(&worktree, worktree.ptr)) < 0 ||
+ git_path_to_dir(&worktree) < 0) {
+ error = -1;
+ goto cleanup;
+ }
+
+ repo->workdir = git_buf_detach(&worktree);
+ }
+ else if (ce && ce->value) {
if ((error = git_path_prettify_dir(
&worktree, ce->value, repo->gitdir)) < 0)
goto cleanup;
@@ -307,6 +326,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
GITERR_CHECK_ALLOC(repo->workdir);
cleanup:
+ git_buf_free(&path);
git_config_entry_free(ce);
return error;
}
@@ -465,6 +485,9 @@ static int find_repo(
git_path_to_dir(&path);
git_buf_set(repo_path, path.ptr, path.size);
+ if (link_path)
+ git_buf_attach(link_path,
+ git_worktree__read_link(path.ptr, GIT_GITDIR_FILE), 0);
if (common_path)
git_buf_swap(&common_link, common_path);
@@ -775,7 +798,11 @@ int git_repository_open_ext(
GITERR_CHECK_ALLOC(repo->commondir);
}
- if (repo->gitlink && repo->commondir && strcmp(repo->gitlink, repo->commondir))
+ if ((error = git_buf_joinpath(&path, repo->gitdir, "gitdir")) < 0)
+ goto cleanup;
+ /* A 'gitdir' file inside a git directory is currently
+ * only used when the repository is a working tree. */
+ if (git_path_exists(path.ptr))
repo->is_worktree = 1;
/*
@@ -801,6 +828,7 @@ int git_repository_open_ext(
}
cleanup:
+ git_buf_free(&path);
git_buf_free(&parent);
git_config_free(config);
diff --git a/src/worktree.c b/src/worktree.c
index 95a2757..a3fe07a 100644
--- a/src/worktree.c
+++ b/src/worktree.c
@@ -61,7 +61,7 @@ exit:
return error;
}
-static char *read_link(const char *base, const char *file)
+char *git_worktree__read_link(const char *base, const char *file)
{
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
@@ -136,8 +136,8 @@ int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *na
}
if ((wt->name = git__strdup(name)) == NULL
- || (wt->commondir_path = read_link(path.ptr, "commondir")) == NULL
- || (wt->gitlink_path = read_link(path.ptr, "gitdir")) == NULL
+ || (wt->commondir_path = git_worktree__read_link(path.ptr, "commondir")) == NULL
+ || (wt->gitlink_path = git_worktree__read_link(path.ptr, "gitdir")) == NULL
|| (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) {
error = -1;
goto out;
diff --git a/src/worktree.h b/src/worktree.h
index 0e1a88d..b8e5279 100644
--- a/src/worktree.h
+++ b/src/worktree.h
@@ -30,4 +30,6 @@ struct git_worktree {
int locked:1;
};
+char *git_worktree__read_link(const char *base, const char *file);
+
#endif
diff --git a/tests/worktree/open.c b/tests/worktree/open.c
index 54a8af4..f5b6681 100644
--- a/tests/worktree/open.c
+++ b/tests/worktree/open.c
@@ -5,10 +5,13 @@
#define WORKTREE_PARENT "submodules-worktree-parent"
#define WORKTREE_CHILD "submodules-worktree-child"
+#define COMMON_REPO "testrepo"
+#define WORKTREE_REPO "testrepo-worktree"
+
void test_worktree_open__repository(void)
{
worktree_fixture fixture =
- WORKTREE_FIXTURE_INIT("testrepo", "testrepo-worktree");
+ WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
setup_fixture_worktree(&fixture);
cl_assert(git_repository_path(fixture.worktree) != NULL);
@@ -20,18 +23,38 @@ void test_worktree_open__repository(void)
cleanup_fixture_worktree(&fixture);
}
+void test_worktree_open__open_discovered_worktree(void)
+{
+ worktree_fixture fixture =
+ WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
+ git_buf path = GIT_BUF_INIT;
+ git_repository *repo;
+
+ setup_fixture_worktree(&fixture);
+
+ cl_git_pass(git_repository_discover(&path,
+ git_repository_workdir(fixture.worktree), false, NULL));
+ cl_git_pass(git_repository_open(&repo, path.ptr));
+ cl_assert_equal_s(git_repository_workdir(fixture.worktree),
+ git_repository_workdir(repo));
+
+ git_buf_free(&path);
+ git_repository_free(repo);
+ cleanup_fixture_worktree(&fixture);
+}
+
void test_worktree_open__repository_with_nonexistent_parent(void)
{
git_repository *repo;
- cl_fixture_sandbox("testrepo-worktree");
- cl_git_pass(p_chdir("testrepo-worktree"));
+ cl_fixture_sandbox(WORKTREE_REPO);
+ cl_git_pass(p_chdir(WORKTREE_REPO));
cl_git_pass(cl_rename(".gitted", ".git"));
cl_git_pass(p_chdir(".."));
- cl_git_fail(git_repository_open(&repo, "testrepo-worktree"));
+ cl_git_fail(git_repository_open(&repo, WORKTREE_REPO));
- cl_fixture_cleanup("testrepo-worktree");
+ cl_fixture_cleanup(WORKTREE_REPO);
}
void test_worktree_open__submodule_worktree_parent(void)
diff --git a/tests/worktree/worktree.c b/tests/worktree/worktree.c
index 81d5929..959c565 100644
--- a/tests/worktree/worktree.c
+++ b/tests/worktree/worktree.c
@@ -217,6 +217,7 @@ void test_worktree_worktree__init(void)
/* Open and verify created repo */
cl_git_pass(git_repository_open(&repo, path.ptr));
+ cl_assert(git__suffixcmp(git_repository_workdir(repo), "worktree-new/") == 0);
cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL));
git_buf_free(&path);