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 static int open_worktree_dir(git_worktree
**out
, const char *parent
, const char *dir
, const char *name
)
126 git_buf gitdir
= GIT_BUF_INIT
;
127 git_worktree
*wt
= NULL
;
130 if (!is_worktree_dir(dir
)) {
135 if ((wt
= git__calloc(1, sizeof(struct git_repository
))) == NULL
) {
140 if ((wt
->name
= git__strdup(name
)) == NULL
141 || (wt
->commondir_path
= git_worktree__read_link(dir
, "commondir")) == NULL
142 || (wt
->gitlink_path
= git_worktree__read_link(dir
, "gitdir")) == NULL
143 || (wt
->parent_path
= git__strdup(parent
)) == NULL
) {
148 if ((error
= git_path_prettify_dir(&gitdir
, dir
, NULL
)) < 0)
150 wt
->gitdir_path
= git_buf_detach(&gitdir
);
152 wt
->locked
= !!git_worktree_is_locked(NULL
, wt
);
158 git_worktree_free(wt
);
159 git_buf_free(&gitdir
);
164 int git_worktree_lookup(git_worktree
**out
, git_repository
*repo
, const char *name
)
166 git_buf path
= GIT_BUF_INIT
;
167 git_worktree
*wt
= NULL
;
170 assert(repo
&& name
);
174 if ((error
= git_buf_printf(&path
, "%s/worktrees/%s", repo
->commondir
, name
)) < 0)
177 if ((error
= (open_worktree_dir(out
, git_repository_workdir(repo
), path
.ptr
, name
))) < 0)
184 git_worktree_free(wt
);
189 int git_worktree_open_from_repository(git_worktree
**out
, git_repository
*repo
)
191 git_buf parent
= GIT_BUF_INIT
;
192 const char *gitdir
, *commondir
;
196 if (!git_repository_is_worktree(repo
)) {
197 giterr_set(GITERR_WORKTREE
, "cannot open worktree of a non-worktree repo");
202 gitdir
= git_repository_path(repo
);
203 commondir
= git_repository_commondir(repo
);
205 if ((error
= git_path_prettify_dir(&parent
, "..", commondir
)) < 0)
208 /* The name is defined by the last component in '.git/worktree/%s' */
209 name
= git_path_basename(gitdir
);
211 if ((error
= open_worktree_dir(out
, parent
.ptr
, gitdir
, name
)) < 0)
216 git_buf_free(&parent
);
221 void git_worktree_free(git_worktree
*wt
)
226 git__free(wt
->commondir_path
);
227 git__free(wt
->gitlink_path
);
228 git__free(wt
->gitdir_path
);
229 git__free(wt
->parent_path
);
234 int git_worktree_validate(const git_worktree
*wt
)
236 git_buf buf
= GIT_BUF_INIT
;
241 git_buf_puts(&buf
, wt
->gitdir_path
);
242 if (!is_worktree_dir(buf
.ptr
)) {
243 giterr_set(GITERR_WORKTREE
,
244 "Worktree gitdir ('%s') is not valid",
250 if (!git_path_exists(wt
->parent_path
)) {
251 giterr_set(GITERR_WORKTREE
,
252 "Worktree parent directory ('%s') does not exist ",
258 if (!git_path_exists(wt
->commondir_path
)) {
259 giterr_set(GITERR_WORKTREE
,
260 "Worktree common directory ('%s') does not exist ",
272 int git_worktree_add_init_options(git_worktree_add_options
*opts
,
273 unsigned int version
)
275 GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts
, version
,
276 git_worktree_add_options
, GIT_WORKTREE_ADD_OPTIONS_INIT
);
280 int git_worktree_add(git_worktree
**out
, git_repository
*repo
,
281 const char *name
, const char *worktree
,
282 const git_worktree_add_options
*opts
)
284 git_buf gitdir
= GIT_BUF_INIT
, wddir
= GIT_BUF_INIT
, buf
= GIT_BUF_INIT
;
285 git_reference
*ref
= NULL
, *head
= NULL
;
286 git_commit
*commit
= NULL
;
287 git_repository
*wt
= NULL
;
288 git_checkout_options coopts
= GIT_CHECKOUT_OPTIONS_INIT
;
289 git_worktree_add_options wtopts
= GIT_WORKTREE_ADD_OPTIONS_INIT
;
292 GITERR_CHECK_VERSION(
293 opts
, GIT_WORKTREE_ADD_OPTIONS_VERSION
, "git_worktree_add_options");
296 memcpy(&wtopts
, opts
, sizeof(wtopts
));
298 assert(out
&& repo
&& name
&& worktree
);
302 /* Create gitdir directory ".git/worktrees/<name>" */
303 if ((err
= git_buf_joinpath(&gitdir
, repo
->commondir
, "worktrees")) < 0)
305 if (!git_path_exists(gitdir
.ptr
))
306 if ((err
= git_futils_mkdir(gitdir
.ptr
, 0755, GIT_MKDIR_EXCL
)) < 0)
308 if ((err
= git_buf_joinpath(&gitdir
, gitdir
.ptr
, name
)) < 0)
310 if ((err
= git_futils_mkdir(gitdir
.ptr
, 0755, GIT_MKDIR_EXCL
)) < 0)
312 if ((err
= git_path_prettify_dir(&gitdir
, gitdir
.ptr
, NULL
)) < 0)
315 /* Create worktree work dir */
316 if ((err
= git_futils_mkdir(worktree
, 0755, GIT_MKDIR_EXCL
)) < 0)
318 if ((err
= git_path_prettify_dir(&wddir
, worktree
, NULL
)) < 0)
324 if ((err
= git_buf_joinpath(&buf
, gitdir
.ptr
, "locked")) < 0)
327 if ((fd
= p_creat(buf
.ptr
, 0644)) < 0) {
336 /* Create worktree .git file */
337 if ((err
= git_buf_printf(&buf
, "gitdir: %s\n", gitdir
.ptr
)) < 0)
339 if ((err
= write_wtfile(wddir
.ptr
, ".git", &buf
)) < 0)
342 /* Create gitdir files */
343 if ((err
= git_path_prettify_dir(&buf
, repo
->commondir
, NULL
) < 0)
344 || (err
= git_buf_putc(&buf
, '\n')) < 0
345 || (err
= write_wtfile(gitdir
.ptr
, "commondir", &buf
)) < 0)
347 if ((err
= git_buf_joinpath(&buf
, wddir
.ptr
, ".git")) < 0
348 || (err
= git_buf_putc(&buf
, '\n')) < 0
349 || (err
= write_wtfile(gitdir
.ptr
, "gitdir", &buf
)) < 0)
352 /* Create new branch */
353 if ((err
= git_repository_head(&head
, repo
)) < 0)
355 if ((err
= git_commit_lookup(&commit
, repo
, &head
->target
.oid
)) < 0)
357 if ((err
= git_branch_create(&ref
, repo
, name
, commit
, false)) < 0)
360 /* Set worktree's HEAD */
361 if ((err
= git_repository_create_head(gitdir
.ptr
, git_reference_name(ref
))) < 0)
363 if ((err
= git_repository_open(&wt
, wddir
.ptr
)) < 0)
366 /* Checkout worktree's HEAD */
367 coopts
.checkout_strategy
= GIT_CHECKOUT_FORCE
;
368 if ((err
= git_checkout_head(wt
, &coopts
)) < 0)
372 if ((err
= git_worktree_lookup(out
, repo
, name
)) < 0)
376 git_buf_free(&gitdir
);
377 git_buf_free(&wddir
);
379 git_reference_free(ref
);
380 git_reference_free(head
);
381 git_commit_free(commit
);
382 git_repository_free(wt
);
387 int git_worktree_lock(git_worktree
*wt
, char *creason
)
389 git_buf buf
= GIT_BUF_INIT
, path
= GIT_BUF_INIT
;
394 if ((err
= git_worktree_is_locked(NULL
, wt
)) < 0)
397 if ((err
= git_buf_joinpath(&path
, wt
->gitdir_path
, "locked")) < 0)
401 git_buf_attach_notowned(&buf
, creason
, strlen(creason
));
403 if ((err
= git_futils_writebuffer(&buf
, path
.ptr
, O_CREAT
|O_EXCL
|O_WRONLY
, 0644)) < 0)
414 int git_worktree_unlock(git_worktree
*wt
)
416 git_buf path
= GIT_BUF_INIT
;
420 if (!git_worktree_is_locked(NULL
, wt
))
423 if (git_buf_joinpath(&path
, wt
->gitdir_path
, "locked") < 0)
426 if (p_unlink(path
.ptr
) != 0) {
438 int git_worktree_is_locked(git_buf
*reason
, const git_worktree
*wt
)
440 git_buf path
= GIT_BUF_INIT
;
446 git_buf_clear(reason
);
448 if ((ret
= git_buf_joinpath(&path
, wt
->gitdir_path
, "locked")) < 0)
450 if ((ret
= git_path_exists(path
.ptr
)) && reason
)
451 git_futils_readbuffer(reason
, path
.ptr
);
459 int git_worktree_prune_init_options(
460 git_worktree_prune_options
*opts
,
461 unsigned int version
)
463 GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts
, version
,
464 git_worktree_prune_options
, GIT_WORKTREE_PRUNE_OPTIONS_INIT
);
468 int git_worktree_is_prunable(git_worktree
*wt
,
469 git_worktree_prune_options
*opts
)
471 git_buf reason
= GIT_BUF_INIT
;
472 git_worktree_prune_options popts
= GIT_WORKTREE_PRUNE_OPTIONS_INIT
;
474 GITERR_CHECK_VERSION(
475 opts
, GIT_WORKTREE_PRUNE_OPTIONS_VERSION
,
476 "git_worktree_prune_options");
479 memcpy(&popts
, opts
, sizeof(popts
));
481 if ((popts
.flags
& GIT_WORKTREE_PRUNE_LOCKED
) == 0 &&
482 git_worktree_is_locked(&reason
, wt
))
485 git_buf_attach_notowned(&reason
, "no reason given", 15);
486 giterr_set(GITERR_WORKTREE
, "Not pruning locked working tree: '%s'", reason
.ptr
);
487 git_buf_free(&reason
);
492 if ((popts
.flags
& GIT_WORKTREE_PRUNE_VALID
) == 0 &&
493 git_worktree_validate(wt
) == 0)
495 giterr_set(GITERR_WORKTREE
, "Not pruning valid working tree");
502 int git_worktree_prune(git_worktree
*wt
,
503 git_worktree_prune_options
*opts
)
505 git_worktree_prune_options popts
= GIT_WORKTREE_PRUNE_OPTIONS_INIT
;
506 git_buf path
= GIT_BUF_INIT
;
510 GITERR_CHECK_VERSION(
511 opts
, GIT_WORKTREE_PRUNE_OPTIONS_VERSION
,
512 "git_worktree_prune_options");
515 memcpy(&popts
, opts
, sizeof(popts
));
517 if (!git_worktree_is_prunable(wt
, &popts
)) {
522 /* Delete gitdir in parent repository */
523 if ((err
= git_buf_printf(&path
, "%s/worktrees/%s", wt
->commondir_path
, wt
->name
)) < 0)
525 if (!git_path_exists(path
.ptr
))
527 giterr_set(GITERR_WORKTREE
, "Worktree gitdir '%s' does not exist", path
.ptr
);
531 if ((err
= git_futils_rmdir_r(path
.ptr
, NULL
, GIT_RMDIR_REMOVE_FILES
)) < 0)
534 /* Skip deletion of the actual working tree if it does
535 * not exist or deletion was not requested */
536 if ((popts
.flags
& GIT_WORKTREE_PRUNE_WORKING_TREE
) == 0 ||
537 !git_path_exists(wt
->gitlink_path
))
542 if ((wtpath
= git_path_dirname(wt
->gitlink_path
)) == NULL
)
544 git_buf_attach(&path
, wtpath
, 0);
545 if (!git_path_exists(path
.ptr
))
547 giterr_set(GITERR_WORKTREE
, "Working tree '%s' does not exist", path
.ptr
);
551 if ((err
= git_futils_rmdir_r(path
.ptr
, NULL
, GIT_RMDIR_REMOVE_FILES
)) < 0)