mirror of
https://git.proxmox.com/git/libgit2
synced 2025-05-03 14:12:22 +00:00

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.
458 lines
12 KiB
C
458 lines
12 KiB
C
#include "clar_libgit2.h"
|
|
#include "worktree_helpers.h"
|
|
|
|
#include "checkout.h"
|
|
#include "repository.h"
|
|
#include "worktree.h"
|
|
|
|
#define COMMON_REPO "testrepo"
|
|
#define WORKTREE_REPO "testrepo-worktree"
|
|
|
|
static worktree_fixture fixture =
|
|
WORKTREE_FIXTURE_INIT(COMMON_REPO, WORKTREE_REPO);
|
|
|
|
void test_worktree_worktree__initialize(void)
|
|
{
|
|
setup_fixture_worktree(&fixture);
|
|
}
|
|
|
|
void test_worktree_worktree__cleanup(void)
|
|
{
|
|
cleanup_fixture_worktree(&fixture);
|
|
}
|
|
|
|
void test_worktree_worktree__list(void)
|
|
{
|
|
git_strarray wts;
|
|
|
|
cl_git_pass(git_worktree_list(&wts, fixture.repo));
|
|
cl_assert_equal_i(wts.count, 1);
|
|
cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
|
|
|
|
git_strarray_free(&wts);
|
|
}
|
|
|
|
void test_worktree_worktree__list_with_invalid_worktree_dirs(void)
|
|
{
|
|
const char *filesets[3][2] = {
|
|
{ "gitdir", "commondir" },
|
|
{ "gitdir", "HEAD" },
|
|
{ "HEAD", "commondir" },
|
|
};
|
|
git_buf path = GIT_BUF_INIT;
|
|
git_strarray wts;
|
|
unsigned i, j, len;
|
|
|
|
cl_git_pass(git_buf_printf(&path, "%s/worktrees/invalid",
|
|
fixture.repo->commondir));
|
|
cl_git_pass(p_mkdir(path.ptr, 0755));
|
|
|
|
len = path.size;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(filesets); i++) {
|
|
|
|
for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) {
|
|
git_buf_truncate(&path, len);
|
|
cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j]));
|
|
cl_git_pass(p_close(p_creat(path.ptr, 0644)));
|
|
}
|
|
|
|
cl_git_pass(git_worktree_list(&wts, fixture.worktree));
|
|
cl_assert_equal_i(wts.count, 1);
|
|
cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
|
|
git_strarray_free(&wts);
|
|
|
|
for (j = 0; j < ARRAY_SIZE(filesets[i]); j++) {
|
|
git_buf_truncate(&path, len);
|
|
cl_git_pass(git_buf_joinpath(&path, path.ptr, filesets[i][j]));
|
|
p_unlink(path.ptr);
|
|
}
|
|
}
|
|
|
|
git_buf_free(&path);
|
|
}
|
|
|
|
void test_worktree_worktree__list_in_worktree_repo(void)
|
|
{
|
|
git_strarray wts;
|
|
|
|
cl_git_pass(git_worktree_list(&wts, fixture.worktree));
|
|
cl_assert_equal_i(wts.count, 1);
|
|
cl_assert_equal_s(wts.strings[0], "testrepo-worktree");
|
|
|
|
git_strarray_free(&wts);
|
|
}
|
|
|
|
void test_worktree_worktree__list_bare(void)
|
|
{
|
|
git_repository *repo;
|
|
git_strarray wts;
|
|
|
|
repo = cl_git_sandbox_init("testrepo.git");
|
|
cl_git_pass(git_worktree_list(&wts, repo));
|
|
cl_assert_equal_i(wts.count, 0);
|
|
|
|
git_repository_free(repo);
|
|
}
|
|
|
|
void test_worktree_worktree__list_without_worktrees(void)
|
|
{
|
|
git_repository *repo;
|
|
git_strarray wts;
|
|
|
|
repo = cl_git_sandbox_init("testrepo2");
|
|
cl_git_pass(git_worktree_list(&wts, repo));
|
|
cl_assert_equal_i(wts.count, 0);
|
|
|
|
git_repository_free(repo);
|
|
}
|
|
|
|
void test_worktree_worktree__lookup(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_buf gitdir_path = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
|
|
git_buf_printf(&gitdir_path, "%s/worktrees/%s", fixture.repo->commondir, "testrepo-worktree");
|
|
|
|
cl_assert_equal_s(wt->gitdir_path, gitdir_path.ptr);
|
|
cl_assert_equal_s(wt->parent_path, fixture.repo->gitdir);
|
|
cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink);
|
|
cl_assert_equal_s(wt->commondir_path, fixture.repo->commondir);
|
|
|
|
git_buf_free(&gitdir_path);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__lookup_nonexistent_worktree(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_fail(git_worktree_lookup(&wt, fixture.repo, "nonexistent"));
|
|
cl_assert_equal_p(wt, NULL);
|
|
}
|
|
|
|
void test_worktree_worktree__open(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_repository *repo;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
|
|
cl_git_pass(git_repository_open_from_worktree(&repo, wt));
|
|
cl_assert_equal_s(git_repository_workdir(repo),
|
|
git_repository_workdir(fixture.worktree));
|
|
|
|
git_repository_free(repo);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__open_invalid_commondir(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_repository *repo;
|
|
git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/commondir"));
|
|
cl_git_pass(git_buf_printf(&path,
|
|
"%s/worktrees/testrepo-worktree/commondir",
|
|
fixture.repo->commondir));
|
|
cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644));
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
|
|
|
git_buf_free(&buf);
|
|
git_buf_free(&path);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__open_invalid_gitdir(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_repository *repo;
|
|
git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/gitdir"));
|
|
cl_git_pass(git_buf_printf(&path,
|
|
"%s/worktrees/testrepo-worktree/gitdir",
|
|
fixture.repo->commondir));
|
|
cl_git_pass(git_futils_writebuffer(&buf, path.ptr, O_RDWR, 0644));
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
|
|
|
git_buf_free(&buf);
|
|
git_buf_free(&path);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__open_invalid_parent(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_repository *repo;
|
|
git_buf buf = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_buf_sets(&buf, "/path/to/nonexistent/gitdir"));
|
|
cl_git_pass(git_futils_writebuffer(&buf,
|
|
fixture.worktree->gitlink, O_RDWR, 0644));
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
|
|
|
git_buf_free(&buf);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__init(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_repository *repo;
|
|
git_reference *branch;
|
|
git_buf path = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
|
|
cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
|
|
|
|
/* 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);
|
|
git_worktree_free(wt);
|
|
git_reference_free(branch);
|
|
git_repository_free(repo);
|
|
}
|
|
|
|
void test_worktree_worktree__init_existing_branch(void)
|
|
{
|
|
git_reference *head, *branch;
|
|
git_commit *commit;
|
|
git_worktree *wt;
|
|
git_buf path = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_repository_head(&head, fixture.repo));
|
|
cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid));
|
|
cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false));
|
|
|
|
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
|
|
cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
|
|
|
|
git_buf_free(&path);
|
|
git_commit_free(commit);
|
|
git_reference_free(head);
|
|
git_reference_free(branch);
|
|
}
|
|
|
|
void test_worktree_worktree__init_existing_worktree(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_buf path = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
|
|
cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr));
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_assert_equal_s(wt->gitlink_path, fixture.worktree->gitlink);
|
|
|
|
git_buf_free(&path);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__init_existing_path(void)
|
|
{
|
|
const char *wtfiles[] = { "HEAD", "commondir", "gitdir", "index" };
|
|
git_worktree *wt;
|
|
git_buf path = GIT_BUF_INIT;
|
|
unsigned i;
|
|
|
|
/* Delete files to verify they have not been created by
|
|
* the init call */
|
|
for (i = 0; i < ARRAY_SIZE(wtfiles); i++) {
|
|
cl_git_pass(git_buf_joinpath(&path,
|
|
fixture.worktree->gitdir, wtfiles[i]));
|
|
cl_git_pass(p_unlink(path.ptr));
|
|
}
|
|
|
|
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../testrepo-worktree"));
|
|
cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
|
|
|
|
/* Verify files have not been re-created */
|
|
for (i = 0; i < ARRAY_SIZE(wtfiles); i++) {
|
|
cl_git_pass(git_buf_joinpath(&path,
|
|
fixture.worktree->gitdir, wtfiles[i]));
|
|
cl_assert(!git_path_exists(path.ptr));
|
|
}
|
|
|
|
git_buf_free(&path);
|
|
}
|
|
|
|
void test_worktree_worktree__validate(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_pass(git_worktree_validate(wt));
|
|
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__validate_invalid_commondir(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
git__free(wt->commondir_path);
|
|
wt->commondir_path = "/path/to/invalid/commondir";
|
|
|
|
cl_git_fail(git_worktree_validate(wt));
|
|
|
|
wt->commondir_path = NULL;
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__validate_invalid_gitdir(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
git__free(wt->gitdir_path);
|
|
wt->gitdir_path = "/path/to/invalid/gitdir";
|
|
cl_git_fail(git_worktree_validate(wt));
|
|
|
|
wt->gitdir_path = NULL;
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__validate_invalid_parent(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
git__free(wt->parent_path);
|
|
wt->parent_path = "/path/to/invalid/parent";
|
|
cl_git_fail(git_worktree_validate(wt));
|
|
|
|
wt->parent_path = NULL;
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__lock_with_reason(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_buf reason = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
|
|
cl_assert(!git_worktree_is_locked(NULL, wt));
|
|
cl_git_pass(git_worktree_lock(wt, "because"));
|
|
cl_assert(git_worktree_is_locked(&reason, wt) > 0);
|
|
cl_assert_equal_s(reason.ptr, "because");
|
|
cl_assert(wt->locked);
|
|
|
|
git_buf_free(&reason);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__lock_without_reason(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_buf reason = GIT_BUF_INIT;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
|
|
cl_assert(!git_worktree_is_locked(NULL, wt));
|
|
cl_git_pass(git_worktree_lock(wt, NULL));
|
|
cl_assert(git_worktree_is_locked(&reason, wt) > 0);
|
|
cl_assert_equal_i(reason.size, 0);
|
|
cl_assert(wt->locked);
|
|
|
|
git_buf_free(&reason);
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__unlock_unlocked_worktree(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_assert(!git_worktree_is_locked(NULL, wt));
|
|
cl_assert(git_worktree_unlock(wt) == 0);
|
|
cl_assert(!wt->locked);
|
|
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__unlock_locked_worktree(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_pass(git_worktree_lock(wt, NULL));
|
|
cl_assert(git_worktree_is_locked(NULL, wt));
|
|
cl_git_pass(git_worktree_unlock(wt));
|
|
cl_assert(!wt->locked);
|
|
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__prune_valid(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_repository *repo;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
|
|
|
|
/* Assert the repository is not valid anymore */
|
|
cl_git_fail(git_repository_open_from_worktree(&repo, wt));
|
|
|
|
git_worktree_free(wt);
|
|
git_repository_free(repo);
|
|
}
|
|
|
|
void test_worktree_worktree__prune_locked(void)
|
|
{
|
|
git_worktree *wt;
|
|
git_repository *repo;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_pass(git_worktree_lock(wt, NULL));
|
|
cl_git_fail(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
|
|
cl_git_fail(git_worktree_prune(wt, ~GIT_WORKTREE_PRUNE_LOCKED));
|
|
|
|
/* Assert the repository is still valid */
|
|
cl_git_pass(git_repository_open_from_worktree(&repo, wt));
|
|
|
|
git_worktree_free(wt);
|
|
git_repository_free(repo);
|
|
}
|
|
|
|
void test_worktree_worktree__prune_gitdir(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_VALID));
|
|
|
|
cl_assert(!git_path_exists(wt->gitdir_path));
|
|
cl_assert(git_path_exists(wt->gitlink_path));
|
|
|
|
git_worktree_free(wt);
|
|
}
|
|
|
|
void test_worktree_worktree__prune_both(void)
|
|
{
|
|
git_worktree *wt;
|
|
|
|
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
|
|
cl_git_pass(git_worktree_prune(wt, GIT_WORKTREE_PRUNE_WORKING_TREE | GIT_WORKTREE_PRUNE_VALID));
|
|
|
|
cl_assert(!git_path_exists(wt->gitdir_path));
|
|
cl_assert(!git_path_exists(wt->gitlink_path));
|
|
|
|
git_worktree_free(wt);
|
|
}
|