File Coverage

deps/libgit2/src/libgit2/worktree.c
Criterion Covered Total %
statement 241 356 67.7
branch 134 252 53.1
condition n/a
subroutine n/a
pod n/a
total 375 608 61.6


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
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.
6             */
7              
8             #include "worktree.h"
9              
10             #include "buf.h"
11             #include "repository.h"
12             #include "path.h"
13              
14             #include "git2/branch.h"
15             #include "git2/commit.h"
16             #include "git2/worktree.h"
17              
18 12           static bool is_worktree_dir(const char *dir)
19             {
20 12           git_str buf = GIT_STR_INIT;
21             int error;
22              
23 12 50         if (git_str_sets(&buf, dir) < 0)
24 0           return -1;
25              
26 12           error = git_fs_path_contains_file(&buf, "commondir")
27 10 50         && git_fs_path_contains_file(&buf, "gitdir")
28 22 100         && git_fs_path_contains_file(&buf, "HEAD");
    50          
29              
30 12           git_str_dispose(&buf);
31 12           return error;
32             }
33              
34 20           int git_worktree_list(git_strarray *wts, git_repository *repo)
35             {
36 20           git_vector worktrees = GIT_VECTOR_INIT;
37 20           git_str path = GIT_STR_INIT;
38             char *worktree;
39             size_t i, len;
40             int error;
41              
42 20 50         GIT_ASSERT_ARG(wts);
43 20 50         GIT_ASSERT_ARG(repo);
44              
45 20           wts->count = 0;
46 20           wts->strings = NULL;
47              
48 20 50         if ((error = git_str_joinpath(&path, repo->commondir, "worktrees/")) < 0)
49 0           goto exit;
50 20 100         if (!git_fs_path_exists(path.ptr) || git_fs_path_is_empty_dir(path.ptr))
    50          
51             goto exit;
52 2 50         if ((error = git_fs_path_dirload(&worktrees, path.ptr, path.size, 0x0)) < 0)
53 0           goto exit;
54              
55 2           len = path.size;
56              
57 6 100         git_vector_foreach(&worktrees, i, worktree) {
58 4           git_str_truncate(&path, len);
59 4           git_str_puts(&path, worktree);
60              
61 4 50         if (!is_worktree_dir(path.ptr)) {
62 0           git_vector_remove(&worktrees, i);
63 0           git__free(worktree);
64             }
65             }
66              
67 2           wts->strings = (char **)git_vector_detach(&wts->count, NULL, &worktrees);
68              
69             exit:
70 20           git_str_dispose(&path);
71              
72 20           return error;
73             }
74              
75 71           char *git_worktree__read_link(const char *base, const char *file)
76             {
77 71           git_str path = GIT_STR_INIT, buf = GIT_STR_INIT;
78              
79 71 50         GIT_ASSERT_ARG_WITH_RETVAL(base, NULL);
80 71 50         GIT_ASSERT_ARG_WITH_RETVAL(file, NULL);
81              
82 71 50         if (git_str_joinpath(&path, base, file) < 0)
83 0           goto err;
84 71 100         if (git_futils_readbuffer(&buf, path.ptr) < 0)
85 58           goto err;
86 13           git_str_dispose(&path);
87              
88 13           git_str_rtrim(&buf);
89              
90 13 50         if (!git_fs_path_is_relative(buf.ptr))
91 13           return git_str_detach(&buf);
92              
93 0 0         if (git_str_sets(&path, base) < 0)
94 0           goto err;
95 0 0         if (git_fs_path_apply_relative(&path, buf.ptr) < 0)
96 0           goto err;
97 0           git_str_dispose(&buf);
98              
99 0           return git_str_detach(&path);
100              
101             err:
102 58           git_str_dispose(&buf);
103 58           git_str_dispose(&path);
104              
105 71           return NULL;
106             }
107              
108 6           static int write_wtfile(const char *base, const char *file, const git_str *buf)
109             {
110 6           git_str path = GIT_STR_INIT;
111             int err;
112              
113 6 50         GIT_ASSERT_ARG(base);
114 6 50         GIT_ASSERT_ARG(file);
115 6 50         GIT_ASSERT_ARG(buf);
116              
117 6 50         if ((err = git_str_joinpath(&path, base, file)) < 0)
118 0           goto out;
119              
120 6 50         if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
121 0           goto out;
122              
123             out:
124 6           git_str_dispose(&path);
125              
126 6           return err;
127             }
128              
129 5           static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name)
130             {
131 5           git_str gitdir = GIT_STR_INIT;
132 5           git_worktree *wt = NULL;
133 5           int error = 0;
134              
135 5 100         if (!is_worktree_dir(dir)) {
136 1           error = -1;
137 1           goto out;
138             }
139              
140 4 50         if ((error = git_path_validate_length(NULL, dir)) < 0)
141 0           goto out;
142              
143 4 50         if ((wt = git__calloc(1, sizeof(*wt))) == NULL) {
144 0           error = -1;
145 0           goto out;
146             }
147              
148 8           if ((wt->name = git__strdup(name)) == NULL ||
149 8 50         (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL ||
150 8 50         (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL ||
151 8           (parent && (wt->parent_path = git__strdup(parent)) == NULL) ||
152 4           (wt->worktree_path = git_fs_path_dirname(wt->gitlink_path)) == NULL) {
153 0           error = -1;
154 0           goto out;
155             }
156              
157 4 50         if ((error = git_fs_path_prettify_dir(&gitdir, dir, NULL)) < 0)
158 0           goto out;
159 4           wt->gitdir_path = git_str_detach(&gitdir);
160              
161 4 50         if ((error = git_worktree_is_locked(NULL, wt)) < 0)
162 0           goto out;
163 4           wt->locked = !!error;
164 4           error = 0;
165              
166 4           *out = wt;
167              
168             out:
169 5 100         if (error)
170 1           git_worktree_free(wt);
171 5           git_str_dispose(&gitdir);
172              
173 5           return error;
174             }
175              
176 5           int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name)
177             {
178 5           git_str path = GIT_STR_INIT;
179 5           git_worktree *wt = NULL;
180             int error;
181              
182 5 50         GIT_ASSERT_ARG(repo);
183 5 50         GIT_ASSERT_ARG(name);
184              
185 5           *out = NULL;
186              
187 5 50         if ((error = git_str_join3(&path, '/', repo->commondir, "worktrees", name)) < 0)
188 0           goto out;
189              
190 5 100         if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0)
191 1           goto out;
192              
193             out:
194 5           git_str_dispose(&path);
195              
196 5 100         if (error)
197 1           git_worktree_free(wt);
198              
199 5           return error;
200             }
201              
202 0           int git_worktree_open_from_repository(git_worktree **out, git_repository *repo)
203             {
204 0           git_str parent = GIT_STR_INIT;
205             const char *gitdir, *commondir;
206 0           char *name = NULL;
207 0           int error = 0;
208              
209 0 0         if (!git_repository_is_worktree(repo)) {
210 0           git_error_set(GIT_ERROR_WORKTREE, "cannot open worktree of a non-worktree repo");
211 0           error = -1;
212 0           goto out;
213             }
214              
215 0           gitdir = git_repository_path(repo);
216 0           commondir = git_repository_commondir(repo);
217              
218 0 0         if ((error = git_fs_path_prettify_dir(&parent, "..", commondir)) < 0)
219 0           goto out;
220              
221             /* The name is defined by the last component in '.git/worktree/%s' */
222 0           name = git_fs_path_basename(gitdir);
223              
224 0 0         if ((error = open_worktree_dir(out, parent.ptr, gitdir, name)) < 0)
225 0           goto out;
226              
227             out:
228 0           git__free(name);
229 0           git_str_dispose(&parent);
230              
231 0           return error;
232             }
233              
234 24           void git_worktree_free(git_worktree *wt)
235             {
236 24 100         if (!wt)
237 20           return;
238              
239 4           git__free(wt->commondir_path);
240 4           git__free(wt->worktree_path);
241 4           git__free(wt->gitlink_path);
242 4           git__free(wt->gitdir_path);
243 4           git__free(wt->parent_path);
244 4           git__free(wt->name);
245 4           git__free(wt);
246             }
247              
248 3           int git_worktree_validate(const git_worktree *wt)
249             {
250 3 50         GIT_ASSERT_ARG(wt);
251              
252 3 100         if (!is_worktree_dir(wt->gitdir_path)) {
253 1           git_error_set(GIT_ERROR_WORKTREE,
254             "worktree gitdir ('%s') is not valid",
255             wt->gitlink_path);
256 1           return GIT_ERROR;
257             }
258              
259 2 50         if (wt->parent_path && !git_fs_path_exists(wt->parent_path)) {
    50          
260 0           git_error_set(GIT_ERROR_WORKTREE,
261             "worktree parent directory ('%s') does not exist ",
262             wt->parent_path);
263 0           return GIT_ERROR;
264             }
265              
266 2 50         if (!git_fs_path_exists(wt->commondir_path)) {
267 0           git_error_set(GIT_ERROR_WORKTREE,
268             "worktree common directory ('%s') does not exist ",
269             wt->commondir_path);
270 0           return GIT_ERROR;
271             }
272              
273 2 50         if (!git_fs_path_exists(wt->worktree_path)) {
274 0           git_error_set(GIT_ERROR_WORKTREE,
275             "worktree directory '%s' does not exist",
276             wt->worktree_path);
277 0           return GIT_ERROR;
278             }
279              
280 2           return 0;
281             }
282              
283 0           int git_worktree_add_options_init(git_worktree_add_options *opts,
284             unsigned int version)
285             {
286 0 0         GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version,
287             git_worktree_add_options, GIT_WORKTREE_ADD_OPTIONS_INIT);
288 0           return 0;
289             }
290              
291             #ifndef GIT_DEPRECATE_HARD
292 0           int git_worktree_add_init_options(git_worktree_add_options *opts,
293             unsigned int version)
294             {
295 0           return git_worktree_add_options_init(opts, version);
296             }
297             #endif
298              
299 2           int git_worktree_add(git_worktree **out, git_repository *repo,
300             const char *name, const char *worktree,
301             const git_worktree_add_options *opts)
302             {
303 2           git_str gitdir = GIT_STR_INIT, wddir = GIT_STR_INIT, buf = GIT_STR_INIT;
304 2           git_reference *ref = NULL, *head = NULL;
305 2           git_commit *commit = NULL;
306 2           git_repository *wt = NULL;
307             git_checkout_options coopts;
308 2           git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT;
309             int err;
310              
311 2 50         GIT_ERROR_CHECK_VERSION(
312             opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options");
313              
314 2 50         GIT_ASSERT_ARG(out);
315 2 50         GIT_ASSERT_ARG(repo);
316 2 50         GIT_ASSERT_ARG(name);
317 2 50         GIT_ASSERT_ARG(worktree);
318              
319 2           *out = NULL;
320              
321 2 50         if (opts)
322 0           memcpy(&wtopts, opts, sizeof(wtopts));
323              
324 2           memcpy(&coopts, &wtopts.checkout_options, sizeof(coopts));
325              
326 2 50         if (wtopts.ref) {
327 0 0         if (!git_reference_is_branch(wtopts.ref)) {
328 0           git_error_set(GIT_ERROR_WORKTREE, "reference is not a branch");
329 0           err = -1;
330 0           goto out;
331             }
332              
333 0 0         if (git_branch_is_checked_out(wtopts.ref)) {
334 0           git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out");
335 0           err = -1;
336 0           goto out;
337             }
338             }
339              
340             /* Create gitdir directory ".git/worktrees/" */
341 2 50         if ((err = git_str_joinpath(&gitdir, repo->commondir, "worktrees")) < 0)
342 0           goto out;
343 2 100         if (!git_fs_path_exists(gitdir.ptr))
344 1 50         if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
345 0           goto out;
346 2 50         if ((err = git_str_joinpath(&gitdir, gitdir.ptr, name)) < 0)
347 0           goto out;
348 2 50         if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
349 0           goto out;
350 2 50         if ((err = git_fs_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0)
351 0           goto out;
352              
353             /* Create worktree work dir */
354 2 50         if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0)
355 0           goto out;
356 2 50         if ((err = git_fs_path_prettify_dir(&wddir, worktree, NULL)) < 0)
357 0           goto out;
358              
359 2 50         if (wtopts.lock) {
360             int fd;
361              
362 0 0         if ((err = git_str_joinpath(&buf, gitdir.ptr, "locked")) < 0)
363 0           goto out;
364              
365 0 0         if ((fd = p_creat(buf.ptr, 0644)) < 0) {
366 0           err = fd;
367 0           goto out;
368             }
369              
370 0           p_close(fd);
371 0           git_str_clear(&buf);
372             }
373              
374             /* Create worktree .git file */
375 2 50         if ((err = git_str_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0)
376 0           goto out;
377 2 50         if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0)
378 0           goto out;
379              
380             /* Create gitdir files */
381 2 50         if ((err = git_fs_path_prettify_dir(&buf, repo->commondir, NULL) < 0)
382 2 50         || (err = git_str_putc(&buf, '\n')) < 0
383 2 50         || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0)
384             goto out;
385 2 50         if ((err = git_str_joinpath(&buf, wddir.ptr, ".git")) < 0
386 2 50         || (err = git_str_putc(&buf, '\n')) < 0
387 2 50         || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0)
388             goto out;
389              
390             /* Set up worktree reference */
391 2 50         if (wtopts.ref) {
392 0 0         if ((err = git_reference_dup(&ref, wtopts.ref)) < 0)
393 0           goto out;
394             } else {
395 2 50         if ((err = git_repository_head(&head, repo)) < 0)
396 0           goto out;
397 2 50         if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0)
398 0           goto out;
399 2 50         if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0)
400 0           goto out;
401             }
402              
403             /* Set worktree's HEAD */
404 2 50         if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0)
405 0           goto out;
406 2 50         if ((err = git_repository_open(&wt, wddir.ptr)) < 0)
407 0           goto out;
408              
409             /* Checkout worktree's HEAD */
410 2 50         if ((err = git_checkout_head(wt, &coopts)) < 0)
411 0           goto out;
412              
413             /* Load result */
414 2 50         if ((err = git_worktree_lookup(out, repo, name)) < 0)
415 0           goto out;
416              
417             out:
418 2           git_str_dispose(&gitdir);
419 2           git_str_dispose(&wddir);
420 2           git_str_dispose(&buf);
421 2           git_reference_free(ref);
422 2           git_reference_free(head);
423 2           git_commit_free(commit);
424 2           git_repository_free(wt);
425              
426 2           return err;
427             }
428              
429 3           int git_worktree_lock(git_worktree *wt, const char *reason)
430             {
431 3           git_str buf = GIT_STR_INIT, path = GIT_STR_INIT;
432             int error;
433              
434 3 50         GIT_ASSERT_ARG(wt);
435              
436 3 50         if ((error = git_worktree_is_locked(NULL, wt)) < 0)
437 0           goto out;
438 3 100         if (error) {
439 1           error = GIT_ELOCKED;
440 1           goto out;
441             }
442              
443 2 50         if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0)
444 0           goto out;
445              
446 2 50         if (reason)
447 2           git_str_attach_notowned(&buf, reason, strlen(reason));
448              
449 2 50         if ((error = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
450 0           goto out;
451              
452 2           wt->locked = 1;
453              
454             out:
455 3           git_str_dispose(&path);
456              
457 3           return error;
458             }
459              
460 1           int git_worktree_unlock(git_worktree *wt)
461             {
462 1           git_str path = GIT_STR_INIT;
463             int error;
464              
465 1 50         GIT_ASSERT_ARG(wt);
466              
467 1 50         if ((error = git_worktree_is_locked(NULL, wt)) < 0)
468 0           return error;
469 1 50         if (!error)
470 0           return 1;
471              
472 1 50         if (git_str_joinpath(&path, wt->gitdir_path, "locked") < 0)
473 0           return -1;
474              
475 1 50         if (p_unlink(path.ptr) != 0) {
476 0           git_str_dispose(&path);
477 0           return -1;
478             }
479              
480 1           wt->locked = 0;
481              
482 1           git_str_dispose(&path);
483              
484 1           return 0;
485             }
486              
487 14           static int git_worktree__is_locked(git_str *reason, const git_worktree *wt)
488             {
489 14           git_str path = GIT_STR_INIT;
490             int error, locked;
491              
492 14 50         GIT_ASSERT_ARG(wt);
493              
494 14 100         if (reason)
495 6           git_str_clear(reason);
496              
497 14 50         if ((error = git_str_joinpath(&path, wt->gitdir_path, "locked")) < 0)
498 0           goto out;
499 14           locked = git_fs_path_exists(path.ptr);
500 14 100         if (locked && reason &&
    100          
    50          
501 2           (error = git_futils_readbuffer(reason, path.ptr)) < 0)
502 0           goto out;
503              
504 14           error = locked;
505             out:
506 14           git_str_dispose(&path);
507              
508 14           return error;
509             }
510              
511 11           int git_worktree_is_locked(git_buf *reason, const git_worktree *wt)
512             {
513 11           git_str str = GIT_STR_INIT;
514 11           int error = 0;
515              
516 11 100         if (reason && (error = git_buf_tostr(&str, reason)) < 0)
    50          
517 0           return error;
518              
519 11 100         error = git_worktree__is_locked(reason ? &str : NULL, wt);
520              
521 11 50         if (error >= 0 && reason) {
    100          
522 3 50         if (git_buf_fromstr(reason, &str) < 0)
523 0           error = -1;
524             }
525              
526 11           git_str_dispose(&str);
527 11           return error;
528             }
529              
530 2           const char *git_worktree_name(const git_worktree *wt)
531             {
532 2 50         GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL);
533 2           return wt->name;
534             }
535              
536 2           const char *git_worktree_path(const git_worktree *wt)
537             {
538 2 50         GIT_ASSERT_ARG_WITH_RETVAL(wt, NULL);
539 2           return wt->worktree_path;
540             }
541              
542 0           int git_worktree_prune_options_init(
543             git_worktree_prune_options *opts,
544             unsigned int version)
545             {
546 0 0         GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version,
547             git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT);
548 0           return 0;
549             }
550              
551             #ifndef GIT_DEPRECATE_HARD
552 0           int git_worktree_prune_init_options(git_worktree_prune_options *opts,
553             unsigned int version)
554             {
555 0           return git_worktree_prune_options_init(opts, version);
556             }
557             #endif
558              
559 6           int git_worktree_is_prunable(git_worktree *wt,
560             git_worktree_prune_options *opts)
561             {
562 6           git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
563              
564 6 50         GIT_ERROR_CHECK_VERSION(
565             opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION,
566             "git_worktree_prune_options");
567              
568 6 50         if (opts)
569 6           memcpy(&popts, opts, sizeof(popts));
570              
571 6 100         if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0) {
572 3           git_str reason = GIT_STR_INIT;
573             int error;
574              
575 3 50         if ((error = git_worktree__is_locked(&reason, wt)) < 0)
576 1           return error;
577              
578 3 100         if (error) {
579 1 50         if (!reason.size)
580 0           git_str_attach_notowned(&reason, "no reason given", 15);
581 1           git_error_set(GIT_ERROR_WORKTREE, "not pruning locked working tree: '%s'", reason.ptr);
582 1           git_str_dispose(&reason);
583 3           return 0;
584             }
585             }
586              
587 6           if ((popts.flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
588 1           git_worktree_validate(wt) == 0) {
589 1           git_error_set(GIT_ERROR_WORKTREE, "not pruning valid working tree");
590 1           return 0;
591             }
592              
593 6           return 1;
594             }
595              
596 2           int git_worktree_prune(git_worktree *wt,
597             git_worktree_prune_options *opts)
598             {
599 2           git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
600 2           git_str path = GIT_STR_INIT;
601             char *wtpath;
602             int err;
603              
604 2 50         GIT_ERROR_CHECK_VERSION(
605             opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION,
606             "git_worktree_prune_options");
607              
608 2 50         if (opts)
609 2           memcpy(&popts, opts, sizeof(popts));
610              
611 2 50         if (!git_worktree_is_prunable(wt, &popts)) {
612 0           err = -1;
613 0           goto out;
614             }
615              
616             /* Delete gitdir in parent repository */
617 2 50         if ((err = git_str_join3(&path, '/', wt->commondir_path, "worktrees", wt->name)) < 0)
618 0           goto out;
619 2 50         if (!git_fs_path_exists(path.ptr))
620             {
621 0           git_error_set(GIT_ERROR_WORKTREE, "worktree gitdir '%s' does not exist", path.ptr);
622 0           err = -1;
623 0           goto out;
624             }
625 2 50         if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
626 0           goto out;
627              
628             /* Skip deletion of the actual working tree if it does
629             * not exist or deletion was not requested */
630 4           if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
631 2           !git_fs_path_exists(wt->gitlink_path))
632             {
633             goto out;
634             }
635              
636 2 50         if ((wtpath = git_fs_path_dirname(wt->gitlink_path)) == NULL)
637 0           goto out;
638 2           git_str_attach(&path, wtpath, 0);
639 2 50         if (!git_fs_path_exists(path.ptr))
640             {
641 0           git_error_set(GIT_ERROR_WORKTREE, "working tree '%s' does not exist", path.ptr);
642 0           err = -1;
643 0           goto out;
644             }
645 2 50         if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
646 0           goto out;
647              
648             out:
649 2           git_str_dispose(&path);
650              
651 2           return err;
652             }