File Coverage

deps/libgit2/src/worktree.c
Criterion Covered Total %
statement 223 333 66.9
branch 123 236 52.1
condition n/a
subroutine n/a
pod n/a
total 346 569 60.8


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