2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
10 #include "git2/branch.h"
11 #include "git2/commit.h"
12 #include "git2/worktree.h"
14 #include "repository.h"
17 static bool is_worktree_dir(const char *dir
)
19 git_buf buf
= GIT_BUF_INIT
;
22 if (git_buf_sets(&buf
, dir
) < 0)
25 error
= git_path_contains_file(&buf
, "commondir")
26 && git_path_contains_file(&buf
, "gitdir")
27 && git_path_contains_file(&buf
, "HEAD");
33 int git_worktree_list(git_strarray
*wts
, git_repository
*repo
)
35 git_vector worktrees
= GIT_VECTOR_INIT
;
36 git_buf path
= GIT_BUF_INIT
;
46 if ((error
= git_buf_printf(&path
, "%s/worktrees/", repo
->commondir
)) < 0)
48 if (!git_path_exists(path
.ptr
) || git_path_is_empty_dir(path
.ptr
))
50 if ((error
= git_path_dirload(&worktrees
, path
.ptr
, path
.size
, 0x0)) < 0)
55 git_vector_foreach(&worktrees
, i
, worktree
) {
56 git_buf_truncate(&path
, len
);
57 git_buf_puts(&path
, worktree
);
59 if (!is_worktree_dir(path
.ptr
)) {
60 git_vector_remove(&worktrees
, i
);
65 wts
->strings
= (char **)git_vector_detach(&wts
->count
, NULL
, &worktrees
);
73 char *git_worktree__read_link(const char *base
, const char *file
)
75 git_buf path
= GIT_BUF_INIT
, buf
= GIT_BUF_INIT
;
79 if (git_buf_joinpath(&path
, base
, file
) < 0)
81 if (git_futils_readbuffer(&buf
, path
.ptr
) < 0)
87 if (!git_path_is_relative(buf
.ptr
))
88 return git_buf_detach(&buf
);
90 if (git_buf_sets(&path
, base
) < 0)
92 if (git_path_apply_relative(&path
, buf
.ptr
) < 0)
96 return git_buf_detach(&path
);
105 static int write_wtfile(const char *base
, const char *file
, const git_buf
*buf
)
107 git_buf path
= GIT_BUF_INIT
;
110 assert(base
&& file
&& buf
);
112 if ((err
= git_buf_joinpath(&path
, base
, file
)) < 0)
115 if ((err
= git_futils_writebuffer(buf
, path
.ptr
, O_CREAT
|O_EXCL
|O_WRONLY
, 0644)) < 0)
124 int git_worktree_lookup(git_worktree
**out
, git_repository
*repo
, const char *name
)
126 git_buf path
= GIT_BUF_INIT
;
127 git_worktree
*wt
= NULL
;
130 assert(repo
&& name
);
134 if ((error
= git_buf_printf(&path
, "%s/worktrees/%s", repo
->commondir
, name
)) < 0)
137 if (!is_worktree_dir(path
.ptr
)) {
142 if ((wt
= git__malloc(sizeof(struct git_repository
))) == NULL
) {
147 if ((wt
->name
= git__strdup(name
)) == NULL
148 || (wt
->commondir_path
= git_worktree__read_link(path
.ptr
, "commondir")) == NULL
149 || (wt
->gitlink_path
= git_worktree__read_link(path
.ptr
, "gitdir")) == NULL
150 || (wt
->parent_path
= git__strdup(git_repository_path(repo
))) == NULL
) {
154 wt
->gitdir_path
= git_buf_detach(&path
);
155 wt
->locked
= !!git_worktree_is_locked(NULL
, wt
);
163 git_worktree_free(wt
);
168 void git_worktree_free(git_worktree
*wt
)
173 git__free(wt
->commondir_path
);
174 git__free(wt
->gitlink_path
);
175 git__free(wt
->gitdir_path
);
176 git__free(wt
->parent_path
);
181 int git_worktree_validate(const git_worktree
*wt
)
183 git_buf buf
= GIT_BUF_INIT
;
188 git_buf_puts(&buf
, wt
->gitdir_path
);
189 if (!is_worktree_dir(buf
.ptr
)) {
190 giterr_set(GITERR_WORKTREE
,
191 "Worktree gitdir ('%s') is not valid",
197 if (!git_path_exists(wt
->parent_path
)) {
198 giterr_set(GITERR_WORKTREE
,
199 "Worktree parent directory ('%s') does not exist ",
205 if (!git_path_exists(wt
->commondir_path
)) {
206 giterr_set(GITERR_WORKTREE
,
207 "Worktree common directory ('%s') does not exist ",
219 int git_worktree_add(git_worktree
**out
, git_repository
*repo
, const char *name
, const char *worktree
)
221 git_buf path
= GIT_BUF_INIT
, buf
= GIT_BUF_INIT
;
222 git_reference
*ref
= NULL
, *head
= NULL
;
223 git_commit
*commit
= NULL
;
224 git_repository
*wt
= NULL
;
225 git_checkout_options coopts
= GIT_CHECKOUT_OPTIONS_INIT
;
228 assert(out
&& repo
&& name
&& worktree
);
232 /* Create worktree related files in commondir */
233 if ((err
= git_buf_joinpath(&path
, repo
->commondir
, "worktrees")) < 0)
235 if (!git_path_exists(path
.ptr
))
236 if ((err
= git_futils_mkdir(path
.ptr
, 0755, GIT_MKDIR_EXCL
)) < 0)
238 if ((err
= git_buf_joinpath(&path
, path
.ptr
, name
)) < 0)
240 if ((err
= git_futils_mkdir(path
.ptr
, 0755, GIT_MKDIR_EXCL
)) < 0)
243 /* Create worktree work dir */
244 if ((err
= git_futils_mkdir(worktree
, 0755, GIT_MKDIR_EXCL
)) < 0)
247 /* Create worktree .git file */
248 if ((err
= git_buf_printf(&buf
, "gitdir: %s\n", path
.ptr
)) < 0)
250 if ((err
= write_wtfile(worktree
, ".git", &buf
)) < 0)
253 /* Create commondir files */
254 if ((err
= git_buf_sets(&buf
, repo
->commondir
)) < 0
255 || (err
= git_buf_putc(&buf
, '\n')) < 0
256 || (err
= write_wtfile(path
.ptr
, "commondir", &buf
)) < 0)
258 if ((err
= git_buf_joinpath(&buf
, worktree
, ".git")) < 0
259 || (err
= git_buf_putc(&buf
, '\n')) < 0
260 || (err
= write_wtfile(path
.ptr
, "gitdir", &buf
)) < 0)
263 /* Create new branch */
264 if ((err
= git_repository_head(&head
, repo
)) < 0)
266 if ((err
= git_commit_lookup(&commit
, repo
, &head
->target
.oid
)) < 0)
268 if ((err
= git_branch_create(&ref
, repo
, name
, commit
, false)) < 0)
271 /* Set worktree's HEAD */
272 if ((err
= git_repository_create_head(path
.ptr
, name
)) < 0)
274 if ((err
= git_repository_open(&wt
, worktree
)) < 0)
277 /* Checkout worktree's HEAD */
278 coopts
.checkout_strategy
= GIT_CHECKOUT_FORCE
;
279 if ((err
= git_checkout_head(wt
, &coopts
)) < 0)
283 if ((err
= git_worktree_lookup(out
, repo
, name
)) < 0)
289 git_reference_free(ref
);
290 git_reference_free(head
);
291 git_commit_free(commit
);
292 git_repository_free(wt
);
297 int git_worktree_lock(git_worktree
*wt
, char *creason
)
299 git_buf buf
= GIT_BUF_INIT
, path
= GIT_BUF_INIT
;
304 if ((err
= git_worktree_is_locked(NULL
, wt
)) < 0)
307 if ((err
= git_buf_joinpath(&path
, wt
->gitdir_path
, "locked")) < 0)
311 git_buf_attach_notowned(&buf
, creason
, strlen(creason
));
313 if ((err
= git_futils_writebuffer(&buf
, path
.ptr
, O_CREAT
|O_EXCL
|O_WRONLY
, 0644)) < 0)
324 int git_worktree_unlock(git_worktree
*wt
)
326 git_buf path
= GIT_BUF_INIT
;
330 if (!git_worktree_is_locked(NULL
, wt
))
333 if (git_buf_joinpath(&path
, wt
->gitdir_path
, "locked") < 0)
336 if (p_unlink(path
.ptr
) != 0) {
348 int git_worktree_is_locked(git_buf
*reason
, const git_worktree
*wt
)
350 git_buf path
= GIT_BUF_INIT
;
356 git_buf_clear(reason
);
358 if ((ret
= git_buf_joinpath(&path
, wt
->gitdir_path
, "locked")) < 0)
360 if ((ret
= git_path_exists(path
.ptr
)) && reason
)
361 git_futils_readbuffer(reason
, path
.ptr
);
369 int git_worktree_is_prunable(git_worktree
*wt
, unsigned flags
)
371 git_buf reason
= GIT_BUF_INIT
;
373 if ((flags
& GIT_WORKTREE_PRUNE_LOCKED
) == 0 &&
374 git_worktree_is_locked(&reason
, wt
))
377 git_buf_attach_notowned(&reason
, "no reason given", 15);
378 giterr_set(GITERR_WORKTREE
, "Not pruning locked working tree: '%s'", reason
.ptr
);
379 git_buf_free(&reason
);
384 if ((flags
& GIT_WORKTREE_PRUNE_VALID
) == 0 &&
385 git_worktree_validate(wt
) == 0)
387 giterr_set(GITERR_WORKTREE
, "Not pruning valid working tree");
394 int git_worktree_prune(git_worktree
*wt
, unsigned flags
)
396 git_buf path
= GIT_BUF_INIT
;
400 if (!git_worktree_is_prunable(wt
, flags
)) {
405 /* Delete gitdir in parent repository */
406 if ((err
= git_buf_printf(&path
, "%s/worktrees/%s", wt
->parent_path
, wt
->name
)) < 0)
408 if (!git_path_exists(path
.ptr
))
410 giterr_set(GITERR_WORKTREE
, "Worktree gitdir '%s' does not exist", path
.ptr
);
414 if ((err
= git_futils_rmdir_r(path
.ptr
, NULL
, GIT_RMDIR_REMOVE_FILES
)) < 0)
417 /* Skip deletion of the actual working tree if it does
418 * not exist or deletion was not requested */
419 if ((flags
& GIT_WORKTREE_PRUNE_WORKING_TREE
) == 0 ||
420 !git_path_exists(wt
->gitlink_path
))
425 if ((wtpath
= git_path_dirname(wt
->gitlink_path
)) == NULL
)
427 git_buf_attach(&path
, wtpath
, 0);
428 if (!git_path_exists(path
.ptr
))
430 giterr_set(GITERR_WORKTREE
, "Working tree '%s' does not exist", path
.ptr
);
434 if ((err
= git_futils_rmdir_r(path
.ptr
, NULL
, GIT_RMDIR_REMOVE_FILES
)) < 0)