Skip to content

Commit dea7488

Browse files
committed
worktree: implement git_worktree_add
Implement the `git_worktree_add` function which can be used to create new working trees for a given repository.
1 parent 372dc9f commit dea7488

File tree

3 files changed

+199
-1
lines changed

3 files changed

+199
-1
lines changed

include/git2/worktree.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,21 @@ GIT_EXTERN(void) git_worktree_free(git_worktree *wt);
6161
*/
6262
GIT_EXTERN(int) git_worktree_validate(const git_worktree *wt);
6363

64+
/**
65+
* Add a new working tree
66+
*
67+
* Add a new working tree for the repository, that is create the
68+
* required data structures inside the repository and check out
69+
* the current HEAD at `path`
70+
*
71+
* @param out Output pointer containing new working tree
72+
* @param repo Repository to create working tree for
73+
* @param name Name of the working tree
74+
* @param path Path to create working tree at
75+
* @return 0 or an error code
76+
*/
77+
GIT_EXTERN(int) git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *path);
78+
6479
/** @} */
6580
GIT_END_DECL
6681
#endif

src/worktree.c

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
* a Linking Exception. For full terms see the included COPYING file.
66
*/
77

8+
#include "common.h"
9+
10+
#include "git2/branch.h"
11+
#include "git2/commit.h"
812
#include "git2/worktree.h"
913

10-
#include "common.h"
1114
#include "repository.h"
1215
#include "worktree.h"
1316

@@ -90,6 +93,25 @@ static char *read_link(const char *base, const char *file)
9093
return NULL;
9194
}
9295

96+
static int write_wtfile(const char *base, const char *file, const git_buf *buf)
97+
{
98+
git_buf path = GIT_BUF_INIT;
99+
int err;
100+
101+
assert(base && file && buf);
102+
103+
if ((err = git_buf_joinpath(&path, base, file)) < 0)
104+
goto out;
105+
106+
if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
107+
goto out;
108+
109+
out:
110+
git_buf_free(&path);
111+
112+
return err;
113+
}
114+
93115
int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name)
94116
{
95117
git_buf path = GIT_BUF_INIT;
@@ -183,3 +205,81 @@ int git_worktree_validate(const git_worktree *wt)
183205

184206
return err;
185207
}
208+
209+
int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree)
210+
{
211+
git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
212+
git_reference *ref = NULL, *head = NULL;
213+
git_commit *commit = NULL;
214+
git_repository *wt = NULL;
215+
git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT;
216+
int err;
217+
218+
assert(out && repo && name && worktree);
219+
220+
*out = NULL;
221+
222+
/* Create worktree related files in commondir */
223+
if ((err = git_buf_joinpath(&path, repo->commondir, "worktrees")) < 0)
224+
goto out;
225+
if (!git_path_exists(path.ptr))
226+
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
227+
goto out;
228+
if ((err = git_buf_joinpath(&path, path.ptr, name)) < 0)
229+
goto out;
230+
if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
231+
goto out;
232+
233+
/* Create worktree work dir */
234+
if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0)
235+
goto out;
236+
237+
/* Create worktree .git file */
238+
if ((err = git_buf_printf(&buf, "gitdir: %s\n", path.ptr)) < 0)
239+
goto out;
240+
if ((err = write_wtfile(worktree, ".git", &buf)) < 0)
241+
goto out;
242+
243+
/* Create commondir files */
244+
if ((err = git_buf_sets(&buf, repo->commondir)) < 0
245+
|| (err = git_buf_putc(&buf, '\n')) < 0
246+
|| (err = write_wtfile(path.ptr, "commondir", &buf)) < 0)
247+
goto out;
248+
if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0
249+
|| (err = git_buf_putc(&buf, '\n')) < 0
250+
|| (err = write_wtfile(path.ptr, "gitdir", &buf)) < 0)
251+
goto out;
252+
253+
/* Create new branch */
254+
if ((err = git_repository_head(&head, repo)) < 0)
255+
goto out;
256+
if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0)
257+
goto out;
258+
if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0)
259+
goto out;
260+
261+
/* Set worktree's HEAD */
262+
if ((err = git_repository_create_head(path.ptr, name)) < 0)
263+
goto out;
264+
if ((err = git_repository_open(&wt, worktree)) < 0)
265+
goto out;
266+
267+
/* Checkout worktree's HEAD */
268+
coopts.checkout_strategy = GIT_CHECKOUT_FORCE;
269+
if ((err = git_checkout_head(wt, &coopts)) < 0)
270+
goto out;
271+
272+
/* Load result */
273+
if ((err = git_worktree_lookup(out, repo, name)) < 0)
274+
goto out;
275+
276+
out:
277+
git_buf_free(&path);
278+
git_buf_free(&buf);
279+
git_reference_free(ref);
280+
git_reference_free(head);
281+
git_commit_free(commit);
282+
git_repository_free(wt);
283+
284+
return err;
285+
}

tests/worktree/worktree.c

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,89 @@ void test_worktree_worktree__open_invalid_parent(void)
204204
git_worktree_free(wt);
205205
}
206206

207+
void test_worktree_worktree__init(void)
208+
{
209+
git_worktree *wt;
210+
git_repository *repo;
211+
git_reference *branch;
212+
git_buf path = GIT_BUF_INIT;
213+
214+
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
215+
cl_git_pass(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
216+
217+
/* Open and verify created repo */
218+
cl_git_pass(git_repository_open(&repo, path.ptr));
219+
cl_git_pass(git_branch_lookup(&branch, repo, "worktree-new", GIT_BRANCH_LOCAL));
220+
221+
git_buf_free(&path);
222+
git_worktree_free(wt);
223+
git_reference_free(branch);
224+
git_repository_free(repo);
225+
}
226+
227+
void test_worktree_worktree__init_existing_branch(void)
228+
{
229+
git_reference *head, *branch;
230+
git_commit *commit;
231+
git_worktree *wt;
232+
git_buf path = GIT_BUF_INIT;
233+
234+
cl_git_pass(git_repository_head(&head, fixture.repo));
235+
cl_git_pass(git_commit_lookup(&commit, fixture.repo, &head->target.oid));
236+
cl_git_pass(git_branch_create(&branch, fixture.repo, "worktree-new", commit, false));
237+
238+
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
239+
cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
240+
241+
git_buf_free(&path);
242+
git_commit_free(commit);
243+
git_reference_free(head);
244+
git_reference_free(branch);
245+
}
246+
247+
void test_worktree_worktree__init_existing_worktree(void)
248+
{
249+
git_worktree *wt;
250+
git_buf path = GIT_BUF_INIT;
251+
252+
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../worktree-new"));
253+
cl_git_fail(git_worktree_add(&wt, fixture.repo, "testrepo-worktree", path.ptr));
254+
255+
cl_git_pass(git_worktree_lookup(&wt, fixture.repo, "testrepo-worktree"));
256+
cl_assert_equal_s(wt->gitlink_path, fixture.worktree->path_gitlink);
257+
258+
git_buf_free(&path);
259+
git_worktree_free(wt);
260+
}
261+
262+
void test_worktree_worktree__init_existing_path(void)
263+
{
264+
const char *wtfiles[] = { "HEAD", "commondir", "gitdir", "index" };
265+
git_worktree *wt;
266+
git_buf path = GIT_BUF_INIT;
267+
unsigned i;
268+
269+
/* Delete files to verify they have not been created by
270+
* the init call */
271+
for (i = 0; i < ARRAY_SIZE(wtfiles); i++) {
272+
cl_git_pass(git_buf_joinpath(&path,
273+
fixture.worktree->path_repository, wtfiles[i]));
274+
cl_git_pass(p_unlink(path.ptr));
275+
}
276+
277+
cl_git_pass(git_buf_joinpath(&path, fixture.repo->workdir, "../testrepo-worktree"));
278+
cl_git_fail(git_worktree_add(&wt, fixture.repo, "worktree-new", path.ptr));
279+
280+
/* Verify files have not been re-created */
281+
for (i = 0; i < ARRAY_SIZE(wtfiles); i++) {
282+
cl_git_pass(git_buf_joinpath(&path,
283+
fixture.worktree->path_repository, wtfiles[i]));
284+
cl_assert(!git_path_exists(path.ptr));
285+
}
286+
287+
git_buf_free(&path);
288+
}
289+
207290
void test_worktree_worktree__validate(void)
208291
{
209292
git_worktree *wt;

0 commit comments

Comments
 (0)