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"
16 static bool is_worktree_dir(const char *dir
)
18 git_buf buf
= GIT_BUF_INIT
;
21 if (git_buf_sets(&buf
, dir
) < 0)
24 error
= git_path_contains_file(&buf
, "commondir")
25 && git_path_contains_file(&buf
, "gitdir")
26 && git_path_contains_file(&buf
, "HEAD");
28 git_buf_dispose(&buf
);
32 int git_worktree_list(git_strarray
*wts
, git_repository
*repo
)
34 git_vector worktrees
= GIT_VECTOR_INIT
;
35 git_buf path
= GIT_BUF_INIT
;
45 if ((error
= git_buf_printf(&path
, "%s/worktrees/", repo
->commondir
)) < 0)
47 if (!git_path_exists(path
.ptr
) || git_path_is_empty_dir(path
.ptr
))
49 if ((error
= git_path_dirload(&worktrees
, path
.ptr
, path
.size
, 0x0)) < 0)
54 git_vector_foreach(&worktrees
, i
, worktree
) {
55 git_buf_truncate(&path
, len
);
56 git_buf_puts(&path
, worktree
);
58 if (!is_worktree_dir(path
.ptr
)) {
59 git_vector_remove(&worktrees
, i
);
64 wts
->strings
= (char **)git_vector_detach(&wts
->count
, NULL
, &worktrees
);
67 git_buf_dispose(&path
);
72 char *git_worktree__read_link(const char *base
, const char *file
)
74 git_buf path
= GIT_BUF_INIT
, buf
= GIT_BUF_INIT
;
78 if (git_buf_joinpath(&path
, base
, file
) < 0)
80 if (git_futils_readbuffer(&buf
, path
.ptr
) < 0)
82 git_buf_dispose(&path
);
86 if (!git_path_is_relative(buf
.ptr
))
87 return git_buf_detach(&buf
);
89 if (git_buf_sets(&path
, base
) < 0)
91 if (git_path_apply_relative(&path
, buf
.ptr
) < 0)
93 git_buf_dispose(&buf
);
95 return git_buf_detach(&path
);
98 git_buf_dispose(&buf
);
99 git_buf_dispose(&path
);
104 static int write_wtfile(const char *base
, const char *file
, const git_buf
*buf
)
106 git_buf path
= GIT_BUF_INIT
;
109 assert(base
&& file
&& buf
);
111 if ((err
= git_buf_joinpath(&path
, base
, file
)) < 0)
114 if ((err
= git_futils_writebuffer(buf
, path
.ptr
, O_CREAT
|O_EXCL
|O_WRONLY
, 0644)) < 0)
118 git_buf_dispose(&path
);
123 static int open_worktree_dir(git_worktree
**out
, const char *parent
, const char *dir
, const char *name
)
125 git_buf gitdir
= GIT_BUF_INIT
;
126 git_worktree
*wt
= NULL
;
129 if (!is_worktree_dir(dir
)) {
134 if ((wt
= git__calloc(1, sizeof(*wt
))) == NULL
) {
139 if ((wt
->name
= git__strdup(name
)) == NULL
||
140 (wt
->commondir_path
= git_worktree__read_link(dir
, "commondir")) == NULL
||
141 (wt
->gitlink_path
= git_worktree__read_link(dir
, "gitdir")) == NULL
||
142 (parent
&& (wt
->parent_path
= git__strdup(parent
)) == NULL
) ||
143 (wt
->worktree_path
= git_path_dirname(wt
->gitlink_path
)) == NULL
) {
148 if ((error
= git_path_prettify_dir(&gitdir
, dir
, NULL
)) < 0)
150 wt
->gitdir_path
= git_buf_detach(&gitdir
);
152 if ((error
= git_worktree_is_locked(NULL
, wt
)) < 0)
154 wt
->locked
= !!error
;
161 git_worktree_free(wt
);
162 git_buf_dispose(&gitdir
);
167 int git_worktree_lookup(git_worktree
**out
, git_repository
*repo
, const char *name
)
169 git_buf path
= GIT_BUF_INIT
;
170 git_worktree
*wt
= NULL
;
173 assert(repo
&& name
);
177 if ((error
= git_buf_printf(&path
, "%s/worktrees/%s", repo
->commondir
, name
)) < 0)
180 if ((error
= (open_worktree_dir(out
, git_repository_workdir(repo
), path
.ptr
, name
))) < 0)
184 git_buf_dispose(&path
);
187 git_worktree_free(wt
);
192 int git_worktree_open_from_repository(git_worktree
**out
, git_repository
*repo
)
194 git_buf parent
= GIT_BUF_INIT
;
195 const char *gitdir
, *commondir
;
199 if (!git_repository_is_worktree(repo
)) {
200 git_error_set(GIT_ERROR_WORKTREE
, "cannot open worktree of a non-worktree repo");
205 gitdir
= git_repository_path(repo
);
206 commondir
= git_repository_commondir(repo
);
208 if ((error
= git_path_prettify_dir(&parent
, "..", commondir
)) < 0)
211 /* The name is defined by the last component in '.git/worktree/%s' */
212 name
= git_path_basename(gitdir
);
214 if ((error
= open_worktree_dir(out
, parent
.ptr
, gitdir
, name
)) < 0)
219 git_buf_dispose(&parent
);
224 void git_worktree_free(git_worktree
*wt
)
229 git__free(wt
->commondir_path
);
230 git__free(wt
->worktree_path
);
231 git__free(wt
->gitlink_path
);
232 git__free(wt
->gitdir_path
);
233 git__free(wt
->parent_path
);
238 int git_worktree_validate(const git_worktree
*wt
)
242 if (!is_worktree_dir(wt
->gitdir_path
)) {
243 git_error_set(GIT_ERROR_WORKTREE
,
244 "worktree gitdir ('%s') is not valid",
249 if (wt
->parent_path
&& !git_path_exists(wt
->parent_path
)) {
250 git_error_set(GIT_ERROR_WORKTREE
,
251 "worktree parent directory ('%s') does not exist ",
256 if (!git_path_exists(wt
->commondir_path
)) {
257 git_error_set(GIT_ERROR_WORKTREE
,
258 "worktree common directory ('%s') does not exist ",
266 int git_worktree_add_options_init(git_worktree_add_options
*opts
,
267 unsigned int version
)
269 GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts
, version
,
270 git_worktree_add_options
, GIT_WORKTREE_ADD_OPTIONS_INIT
);
274 #ifndef GIT_DEPRECATE_HARD
275 int git_worktree_add_init_options(git_worktree_add_options
*opts
,
276 unsigned int version
)
278 return git_worktree_add_options_init(opts
, version
);
282 int git_worktree_add(git_worktree
**out
, git_repository
*repo
,
283 const char *name
, const char *worktree
,
284 const git_worktree_add_options
*opts
)
286 git_buf gitdir
= GIT_BUF_INIT
, wddir
= GIT_BUF_INIT
, buf
= GIT_BUF_INIT
;
287 git_reference
*ref
= NULL
, *head
= NULL
;
288 git_commit
*commit
= NULL
;
289 git_repository
*wt
= NULL
;
290 git_checkout_options coopts
= GIT_CHECKOUT_OPTIONS_INIT
;
291 git_worktree_add_options wtopts
= GIT_WORKTREE_ADD_OPTIONS_INIT
;
294 GIT_ERROR_CHECK_VERSION(
295 opts
, GIT_WORKTREE_ADD_OPTIONS_VERSION
, "git_worktree_add_options");
298 memcpy(&wtopts
, opts
, sizeof(wtopts
));
300 assert(out
&& repo
&& name
&& worktree
);
305 if (!git_reference_is_branch(wtopts
.ref
)) {
306 git_error_set(GIT_ERROR_WORKTREE
, "reference is not a branch");
311 if (git_branch_is_checked_out(wtopts
.ref
)) {
312 git_error_set(GIT_ERROR_WORKTREE
, "reference is already checked out");
318 /* Create gitdir directory ".git/worktrees/<name>" */
319 if ((err
= git_buf_joinpath(&gitdir
, repo
->commondir
, "worktrees")) < 0)
321 if (!git_path_exists(gitdir
.ptr
))
322 if ((err
= git_futils_mkdir(gitdir
.ptr
, 0755, GIT_MKDIR_EXCL
)) < 0)
324 if ((err
= git_buf_joinpath(&gitdir
, gitdir
.ptr
, name
)) < 0)
326 if ((err
= git_futils_mkdir(gitdir
.ptr
, 0755, GIT_MKDIR_EXCL
)) < 0)
328 if ((err
= git_path_prettify_dir(&gitdir
, gitdir
.ptr
, NULL
)) < 0)
331 /* Create worktree work dir */
332 if ((err
= git_futils_mkdir(worktree
, 0755, GIT_MKDIR_EXCL
)) < 0)
334 if ((err
= git_path_prettify_dir(&wddir
, worktree
, NULL
)) < 0)
340 if ((err
= git_buf_joinpath(&buf
, gitdir
.ptr
, "locked")) < 0)
343 if ((fd
= p_creat(buf
.ptr
, 0644)) < 0) {
352 /* Create worktree .git file */
353 if ((err
= git_buf_printf(&buf
, "gitdir: %s\n", gitdir
.ptr
)) < 0)
355 if ((err
= write_wtfile(wddir
.ptr
, ".git", &buf
)) < 0)
358 /* Create gitdir files */
359 if ((err
= git_path_prettify_dir(&buf
, repo
->commondir
, NULL
) < 0)
360 || (err
= git_buf_putc(&buf
, '\n')) < 0
361 || (err
= write_wtfile(gitdir
.ptr
, "commondir", &buf
)) < 0)
363 if ((err
= git_buf_joinpath(&buf
, wddir
.ptr
, ".git")) < 0
364 || (err
= git_buf_putc(&buf
, '\n')) < 0
365 || (err
= write_wtfile(gitdir
.ptr
, "gitdir", &buf
)) < 0)
368 /* Set up worktree reference */
370 if ((err
= git_reference_dup(&ref
, wtopts
.ref
)) < 0)
373 if ((err
= git_repository_head(&head
, repo
)) < 0)
375 if ((err
= git_commit_lookup(&commit
, repo
, &head
->target
.oid
)) < 0)
377 if ((err
= git_branch_create(&ref
, repo
, name
, commit
, false)) < 0)
381 /* Set worktree's HEAD */
382 if ((err
= git_repository_create_head(gitdir
.ptr
, git_reference_name(ref
))) < 0)
384 if ((err
= git_repository_open(&wt
, wddir
.ptr
)) < 0)
387 /* Checkout worktree's HEAD */
388 coopts
.checkout_strategy
= GIT_CHECKOUT_FORCE
;
389 if ((err
= git_checkout_head(wt
, &coopts
)) < 0)
393 if ((err
= git_worktree_lookup(out
, repo
, name
)) < 0)
397 git_buf_dispose(&gitdir
);
398 git_buf_dispose(&wddir
);
399 git_buf_dispose(&buf
);
400 git_reference_free(ref
);
401 git_reference_free(head
);
402 git_commit_free(commit
);
403 git_repository_free(wt
);
408 int git_worktree_lock(git_worktree
*wt
, const char *reason
)
410 git_buf buf
= GIT_BUF_INIT
, path
= GIT_BUF_INIT
;
415 if ((error
= git_worktree_is_locked(NULL
, wt
)) < 0)
422 if ((error
= git_buf_joinpath(&path
, wt
->gitdir_path
, "locked")) < 0)
426 git_buf_attach_notowned(&buf
, reason
, strlen(reason
));
428 if ((error
= git_futils_writebuffer(&buf
, path
.ptr
, O_CREAT
|O_EXCL
|O_WRONLY
, 0644)) < 0)
434 git_buf_dispose(&path
);
439 int git_worktree_unlock(git_worktree
*wt
)
441 git_buf path
= GIT_BUF_INIT
;
446 if ((error
= git_worktree_is_locked(NULL
, wt
)) < 0)
451 if (git_buf_joinpath(&path
, wt
->gitdir_path
, "locked") < 0)
454 if (p_unlink(path
.ptr
) != 0) {
455 git_buf_dispose(&path
);
461 git_buf_dispose(&path
);
466 int git_worktree_is_locked(git_buf
*reason
, const git_worktree
*wt
)
468 git_buf path
= GIT_BUF_INIT
;
474 git_buf_clear(reason
);
476 if ((error
= git_buf_joinpath(&path
, wt
->gitdir_path
, "locked")) < 0)
478 locked
= git_path_exists(path
.ptr
);
479 if (locked
&& reason
&&
480 (error
= git_futils_readbuffer(reason
, path
.ptr
)) < 0)
485 git_buf_dispose(&path
);
490 const char *git_worktree_name(const git_worktree
*wt
)
496 const char *git_worktree_path(const git_worktree
*wt
)
499 return wt
->worktree_path
;
502 int git_worktree_prune_options_init(
503 git_worktree_prune_options
*opts
,
504 unsigned int version
)
506 GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts
, version
,
507 git_worktree_prune_options
, GIT_WORKTREE_PRUNE_OPTIONS_INIT
);
511 #ifndef GIT_DEPRECATE_HARD
512 int git_worktree_prune_init_options(git_worktree_prune_options
*opts
,
513 unsigned int version
)
515 return git_worktree_prune_options_init(opts
, version
);
519 int git_worktree_is_prunable(git_worktree
*wt
,
520 git_worktree_prune_options
*opts
)
522 git_worktree_prune_options popts
= GIT_WORKTREE_PRUNE_OPTIONS_INIT
;
524 GIT_ERROR_CHECK_VERSION(
525 opts
, GIT_WORKTREE_PRUNE_OPTIONS_VERSION
,
526 "git_worktree_prune_options");
529 memcpy(&popts
, opts
, sizeof(popts
));
531 if ((popts
.flags
& GIT_WORKTREE_PRUNE_LOCKED
) == 0) {
532 git_buf reason
= GIT_BUF_INIT
;
535 if ((error
= git_worktree_is_locked(&reason
, wt
)) < 0)
540 git_buf_attach_notowned(&reason
, "no reason given", 15);
541 git_error_set(GIT_ERROR_WORKTREE
, "not pruning locked working tree: '%s'", reason
.ptr
);
542 git_buf_dispose(&reason
);
547 if ((popts
.flags
& GIT_WORKTREE_PRUNE_VALID
) == 0 &&
548 git_worktree_validate(wt
) == 0) {
549 git_error_set(GIT_ERROR_WORKTREE
, "not pruning valid working tree");
556 int git_worktree_prune(git_worktree
*wt
,
557 git_worktree_prune_options
*opts
)
559 git_worktree_prune_options popts
= GIT_WORKTREE_PRUNE_OPTIONS_INIT
;
560 git_buf path
= GIT_BUF_INIT
;
564 GIT_ERROR_CHECK_VERSION(
565 opts
, GIT_WORKTREE_PRUNE_OPTIONS_VERSION
,
566 "git_worktree_prune_options");
569 memcpy(&popts
, opts
, sizeof(popts
));
571 if (!git_worktree_is_prunable(wt
, &popts
)) {
576 /* Delete gitdir in parent repository */
577 if ((err
= git_buf_printf(&path
, "%s/worktrees/%s", wt
->commondir_path
, wt
->name
)) < 0)
579 if (!git_path_exists(path
.ptr
))
581 git_error_set(GIT_ERROR_WORKTREE
, "worktree gitdir '%s' does not exist", path
.ptr
);
585 if ((err
= git_futils_rmdir_r(path
.ptr
, NULL
, GIT_RMDIR_REMOVE_FILES
)) < 0)
588 /* Skip deletion of the actual working tree if it does
589 * not exist or deletion was not requested */
590 if ((popts
.flags
& GIT_WORKTREE_PRUNE_WORKING_TREE
) == 0 ||
591 !git_path_exists(wt
->gitlink_path
))
596 if ((wtpath
= git_path_dirname(wt
->gitlink_path
)) == NULL
)
598 git_buf_attach(&path
, wtpath
, 0);
599 if (!git_path_exists(path
.ptr
))
601 git_error_set(GIT_ERROR_WORKTREE
, "working tree '%s' does not exist", path
.ptr
);
605 if ((err
= git_futils_rmdir_r(path
.ptr
, NULL
, GIT_RMDIR_REMOVE_FILES
)) < 0)
609 git_buf_dispose(&path
);