Commit dea7488e by Patrick Steinhardt

worktree: implement `git_worktree_add`

Implement the `git_worktree_add` function which can be used to create
new working trees for a given repository.
parent 372dc9ff
...@@ -61,6 +61,21 @@ GIT_EXTERN(void) git_worktree_free(git_worktree *wt); ...@@ -61,6 +61,21 @@ GIT_EXTERN(void) git_worktree_free(git_worktree *wt);
*/ */
GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt); GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt);
/**
* Add a new working tree
*
* Add a new working tree for the repository, that is create the
* required data structures inside the repository and check out
* the current HEAD at `path`
*
* @param out Output pointer containing new working tree
* @param repo Repository to create working tree for
* @param name Name of the working tree
* @param path Path to create working tree at
* @return 0 or an error code
*/
GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path);
/** @} */ /** @} */
GIT_END_DECL GIT_END_DECL
#endif #endif
...@@ -5,9 +5,12 @@ ...@@ -5,9 +5,12 @@
* a Linking Exception. For full terms see the included COPYING file. * a Linking Exception. For full terms see the included COPYING file.
*/ */
#include "common.h"
#include "git2/branch.h"
#include "git2/commit.h"
#include "git2/worktree.h" #include "git2/worktree.h"
#include "common.h"
#include "repository.h" #include "repository.h"
#include "worktree.h" #include "worktree.h"
...@@ -90,6 +93,25 @@ err: ...@@ -90,6 +93,25 @@ err:
return NULL; return NULL;
} }
static int write_wtfile(const char *base, const char *file, const git_buf *buf)
{
git_buf path = GIT_BUF_INIT;
int err;
assert(base && file && buf);
if ((err = git_buf_joinpath(&path, base, file)) < 0)
goto out;
if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
goto out;
out:
git_buf_free(&path);
return err;
}
int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name) int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name)
{ {
git_buf path = GIT_BUF_INIT; git_buf path = GIT_BUF_INIT;
...@@ -183,3 +205,81 @@ out: ...@@ -183,3 +205,81 @@ out:
return err; return err;
} }
int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree)
{
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
git_reference *ref = NULL, *head = NULL;
git_commit *commit = NULL;
git_repository *wt = NULL;
git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT;
int err;
assert(out && repo && name && worktree);
*out = NULL;
/* Create worktree related files in commondir */
if ((err = git_buf_joinpath(&path, repo->commondir, "worktrees")) < 0)
goto out;
if (!git_path_exists(path.ptr))
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
goto out;
if ((err = git_buf_joinpath(&path, path.ptr, name)) < 0)
goto out;
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
goto out;
/* Create worktree work dir */
if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0)
goto out;
/* Create worktree .git file */
if ((err = git_buf_printf(&buf, "gitdir: %s\n", path.ptr)) < 0)
goto out;
if ((err = write_wtfile(worktree, ".git", &buf)) < 0)
goto out;
/* Create commondir files */
if ((err = git_buf_sets(&buf, repo->commondir)) < 0
|| (err = git_buf_putc(&buf, '\n')) < 0
|| (err = write_wtfile(path.ptr, "commondir", &buf)) < 0)
goto out;
if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0
|| (err = git_buf_putc(&buf, '\n')) < 0
|| (err = write_wtfile(path.ptr, "gitdir", &buf)) < 0)
goto out;
/* Create new branch */
if ((err = git_repository_head(&head, repo)) < 0)
goto out;
if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0)
goto out;
if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0)
goto out;
/* Set worktree's HEAD */
if ((err = git_repository_create_head(path.ptr, name)) < 0)
goto out;
if ((err = git_repository_open(&wt, worktree)) < 0)
goto out;
/* Checkout worktree's HEAD */
coopts.checkout_strategy = GIT_CHECKOUT_FORCE;
if ((err = git_checkout_head(wt, &coopts)) < 0)
goto out;
/* Load result */
if ((err = git_worktree_lookup(out, repo, name)) < 0)
goto out;
out:
git_buf_free(&path);
git_buf_free(&buf);
git_reference_free(ref);
git_reference_free(head);
git_commit_free(commit);
git_repository_free(wt);
return err;
}
...@@ -204,6 +204,89 @@ void test_worktree_worktree__open_invalid_parent(void) ...@@ -204,6 +204,89 @@ void test_worktree_worktree__open_invalid_parent(void)
git_worktree_free(wt); 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_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->path_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->path_repository, 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->path_repository, wtfiles[i]));
cl_assert(!git_path_exists(path.ptr));
}
git_buf_free(&path);
}
void test_worktree_worktree__validate(void) void test_worktree_worktree__validate(void)
{ {
git_worktree *wt; git_worktree *wt;
......
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