Commit 39abd3ad by Patrick Steinhardt

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.
parent 84f56cb0
......@@ -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:"
......@@ -278,6 +279,7 @@ static int load_workdir(git_repository *repo, git_config *config, git_buf *paren
int error;
git_config_entry *ce;
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);
......
......@@ -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;
......
......@@ -30,4 +30,6 @@ struct git_worktree {
int locked:1;
};
char *git_worktree__read_link(const char *base, const char *file);
#endif
......@@ -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)
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment