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 11           static bool is_worktree_dir(const char *dir)
17             {
18 11           git_buf buf = GIT_BUF_INIT;
19             int error;
20              
21 11 50         if (git_buf_sets(&buf, dir) < 0)
22 0           return -1;
23              
24 11           error = git_path_contains_file(&buf, "commondir")
25 9 50         && git_path_contains_file(&buf, "gitdir")
26 20 100         && git_path_contains_file(&buf, "HEAD");
    50          
27              
28 11           git_buf_dispose(&buf);
29 11           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 50           char *git_worktree__read_link(const char *base, const char *file)
73             {
74 50           git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
75              
76 50 50         assert(base && file);
    50          
77              
78 50 50         if (git_buf_joinpath(&path, base, file) < 0)
79 0           goto err;
80 50 100         if (git_futils_readbuffer(&buf, path.ptr) < 0)
81 40           goto err;
82 10           git_buf_dispose(&path);
83              
84 10           git_buf_rtrim(&buf);
85              
86 10 50         if (!git_path_is_relative(buf.ptr))
87 10           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 40           git_buf_dispose(&buf);
99 40           git_buf_dispose(&path);
100              
101 50           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 4           static int open_worktree_dir(git_worktree **out, const char *parent, const char *dir, const char *name)
124             {
125 4           git_buf gitdir = GIT_BUF_INIT;
126 4           git_worktree *wt = NULL;
127 4           int error = 0;
128              
129 4 100         if (!is_worktree_dir(dir)) {
130 1           error = -1;
131 1           goto out;
132             }
133              
134 3 50         if ((wt = git__calloc(1, sizeof(*wt))) == NULL) {
135 0           error = -1;
136 0           goto out;
137             }
138              
139 6           if ((wt->name = git__strdup(name)) == NULL ||
140 6 50         (wt->commondir_path = git_worktree__read_link(dir, "commondir")) == NULL ||
141 6 50         (wt->gitlink_path = git_worktree__read_link(dir, "gitdir")) == NULL ||
142 6           (parent && (wt->parent_path = git__strdup(parent)) == NULL) ||
143 3           (wt->worktree_path = git_path_dirname(wt->gitlink_path)) == NULL) {
144 0           error = -1;
145 0           goto out;
146             }
147              
148 3 50         if ((error = git_path_prettify_dir(&gitdir, dir, NULL)) < 0)
149 0           goto out;
150 3           wt->gitdir_path = git_buf_detach(&gitdir);
151              
152 3 50         if ((error = git_worktree_is_locked(NULL, wt)) < 0)
153 0           goto out;
154 3           wt->locked = !!error;
155 3           error = 0;
156              
157 3           *out = wt;
158              
159             out:
160 4 100         if (error)
161 1           git_worktree_free(wt);
162 4           git_buf_dispose(&gitdir);
163              
164 4           return error;
165             }
166              
167 4           int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name)
168             {
169 4           git_buf path = GIT_BUF_INIT;
170 4           git_worktree *wt = NULL;
171             int error;
172              
173 4 50         assert(repo && name);
    50          
174              
175 4           *out = NULL;
176              
177 4 50         if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0)
178 0           goto out;
179              
180 4 100         if ((error = (open_worktree_dir(out, git_repository_workdir(repo), path.ptr, name))) < 0)
181 1           goto out;
182              
183             out:
184 4           git_buf_dispose(&path);
185              
186 4 100         if (error)
187 1           git_worktree_free(wt);
188              
189 4           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 5           void git_worktree_free(git_worktree *wt)
225             {
226 5 100         if (!wt)
227 2           return;
228              
229 3           git__free(wt->commondir_path);
230 3           git__free(wt->worktree_path);
231 3           git__free(wt->gitlink_path);
232 3           git__free(wt->gitdir_path);
233 3           git__free(wt->parent_path);
234 3           git__free(wt->name);
235 3           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 0           int git_worktree_add_init_options(git_worktree_add_options *opts,
275             unsigned int version)
276             {
277 0           return git_worktree_add_options_init(opts, version);
278             }
279              
280 2           int git_worktree_add(git_worktree **out, git_repository *repo,
281             const char *name, const char *worktree,
282             const git_worktree_add_options *opts)
283             {
284 2           git_buf gitdir = GIT_BUF_INIT, wddir = GIT_BUF_INIT, buf = GIT_BUF_INIT;
285 2           git_reference *ref = NULL, *head = NULL;
286 2           git_commit *commit = NULL;
287 2           git_repository *wt = NULL;
288 2           git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT;
289 2           git_worktree_add_options wtopts = GIT_WORKTREE_ADD_OPTIONS_INIT;
290             int err;
291              
292 2 50         GIT_ERROR_CHECK_VERSION(
293             opts, GIT_WORKTREE_ADD_OPTIONS_VERSION, "git_worktree_add_options");
294              
295 2 50         if (opts)
296 0           memcpy(&wtopts, opts, sizeof(wtopts));
297              
298 2 50         assert(out && repo && name && worktree);
    50          
    50          
    50          
299              
300 2           *out = NULL;
301              
302 2 50         if (wtopts.ref) {
303 0 0         if (!git_reference_is_branch(wtopts.ref)) {
304 0           git_error_set(GIT_ERROR_WORKTREE, "reference is not a branch");
305 0           err = -1;
306 0           goto out;
307             }
308              
309 0 0         if (git_branch_is_checked_out(wtopts.ref)) {
310 0           git_error_set(GIT_ERROR_WORKTREE, "reference is already checked out");
311 0           err = -1;
312 0           goto out;
313             }
314             }
315              
316             /* Create gitdir directory ".git/worktrees/" */
317 2 50         if ((err = git_buf_joinpath(&gitdir, repo->commondir, "worktrees")) < 0)
318 0           goto out;
319 2 100         if (!git_path_exists(gitdir.ptr))
320 1 50         if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
321 0           goto out;
322 2 50         if ((err = git_buf_joinpath(&gitdir, gitdir.ptr, name)) < 0)
323 0           goto out;
324 2 50         if ((err = git_futils_mkdir(gitdir.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
325 0           goto out;
326 2 50         if ((err = git_path_prettify_dir(&gitdir, gitdir.ptr, NULL)) < 0)
327 0           goto out;
328              
329             /* Create worktree work dir */
330 2 50         if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0)
331 0           goto out;
332 2 50         if ((err = git_path_prettify_dir(&wddir, worktree, NULL)) < 0)
333 0           goto out;
334              
335 2 50         if (wtopts.lock) {
336             int fd;
337              
338 0 0         if ((err = git_buf_joinpath(&buf, gitdir.ptr, "locked")) < 0)
339 0           goto out;
340              
341 0 0         if ((fd = p_creat(buf.ptr, 0644)) < 0) {
342 0           err = fd;
343 0           goto out;
344             }
345              
346 0           p_close(fd);
347 0           git_buf_clear(&buf);
348             }
349              
350             /* Create worktree .git file */
351 2 50         if ((err = git_buf_printf(&buf, "gitdir: %s\n", gitdir.ptr)) < 0)
352 0           goto out;
353 2 50         if ((err = write_wtfile(wddir.ptr, ".git", &buf)) < 0)
354 0           goto out;
355              
356             /* Create gitdir files */
357 2 50         if ((err = git_path_prettify_dir(&buf, repo->commondir, NULL) < 0)
358 2 50         || (err = git_buf_putc(&buf, '\n')) < 0
359 2 50         || (err = write_wtfile(gitdir.ptr, "commondir", &buf)) < 0)
360             goto out;
361 2 50         if ((err = git_buf_joinpath(&buf, wddir.ptr, ".git")) < 0
362 2 50         || (err = git_buf_putc(&buf, '\n')) < 0
363 2 50         || (err = write_wtfile(gitdir.ptr, "gitdir", &buf)) < 0)
364             goto out;
365              
366             /* Set up worktree reference */
367 2 50         if (wtopts.ref) {
368 0 0         if ((err = git_reference_dup(&ref, wtopts.ref)) < 0)
369 0           goto out;
370             } else {
371 2 50         if ((err = git_repository_head(&head, repo)) < 0)
372 0           goto out;
373 2 50         if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0)
374 0           goto out;
375 2 50         if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0)
376 0           goto out;
377             }
378              
379             /* Set worktree's HEAD */
380 2 50         if ((err = git_repository_create_head(gitdir.ptr, git_reference_name(ref))) < 0)
381 0           goto out;
382 2 50         if ((err = git_repository_open(&wt, wddir.ptr)) < 0)
383 0           goto out;
384              
385             /* Checkout worktree's HEAD */
386 2           coopts.checkout_strategy = GIT_CHECKOUT_FORCE;
387 2 50         if ((err = git_checkout_head(wt, &coopts)) < 0)
388 0           goto out;
389              
390             /* Load result */
391 2 50         if ((err = git_worktree_lookup(out, repo, name)) < 0)
392 0           goto out;
393              
394             out:
395 2           git_buf_dispose(&gitdir);
396 2           git_buf_dispose(&wddir);
397 2           git_buf_dispose(&buf);
398 2           git_reference_free(ref);
399 2           git_reference_free(head);
400 2           git_commit_free(commit);
401 2           git_repository_free(wt);
402              
403 2           return err;
404             }
405              
406 3           int git_worktree_lock(git_worktree *wt, const char *reason)
407             {
408 3           git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
409             int error;
410              
411 3 50         assert(wt);
412              
413 3 50         if ((error = git_worktree_is_locked(NULL, wt)) < 0)
414 0           goto out;
415 3 100         if (error) {
416 1           error = GIT_ELOCKED;
417 1           goto out;
418             }
419              
420 2 50         if ((error = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0)
421 0           goto out;
422              
423 2 50         if (reason)
424 2           git_buf_attach_notowned(&buf, reason, strlen(reason));
425              
426 2 50         if ((error = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
427 0           goto out;
428              
429 2           wt->locked = 1;
430              
431             out:
432 3           git_buf_dispose(&path);
433              
434 3           return error;
435             }
436              
437 1           int git_worktree_unlock(git_worktree *wt)
438             {
439 1           git_buf path = GIT_BUF_INIT;
440             int error;
441              
442 1 50         assert(wt);
443              
444 1 50         if ((error = git_worktree_is_locked(NULL, wt)) < 0)
445 0           return error;
446 1 50         if (!error)
447 0           return 1;
448              
449 1 50         if (git_buf_joinpath(&path, wt->gitdir_path, "locked") < 0)
450 0           return -1;
451              
452 1 50         if (p_unlink(path.ptr) != 0) {
453 0           git_buf_dispose(&path);
454 0           return -1;
455             }
456              
457 1           wt->locked = 0;
458              
459 1           git_buf_dispose(&path);
460              
461 1           return 0;
462             }
463              
464 13           int git_worktree_is_locked(git_buf *reason, const git_worktree *wt)
465             {
466 13           git_buf path = GIT_BUF_INIT;
467             int error, locked;
468              
469 13 50         assert(wt);
470              
471 13 100         if (reason)
472 6           git_buf_clear(reason);
473              
474 13 50         if ((error = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0)
475 0           goto out;
476 13           locked = git_path_exists(path.ptr);
477 13 100         if (locked && reason &&
    100          
    50          
478 2           (error = git_futils_readbuffer(reason, path.ptr)) < 0)
479 0           goto out;
480              
481 13           error = locked;
482             out:
483 13           git_buf_dispose(&path);
484              
485 13           return error;
486             }
487              
488 2           const char *git_worktree_name(const git_worktree *wt)
489             {
490 2 50         assert(wt);
491 2           return wt->name;
492             }
493              
494 2           const char *git_worktree_path(const git_worktree *wt)
495             {
496 2 50         assert(wt);
497 2           return wt->worktree_path;
498             }
499              
500 0           int git_worktree_prune_options_init(
501             git_worktree_prune_options *opts,
502             unsigned int version)
503             {
504 0 0         GIT_INIT_STRUCTURE_FROM_TEMPLATE(opts, version,
505             git_worktree_prune_options, GIT_WORKTREE_PRUNE_OPTIONS_INIT);
506 0           return 0;
507             }
508              
509 0           int git_worktree_prune_init_options(git_worktree_prune_options *opts,
510             unsigned int version)
511             {
512 0           return git_worktree_prune_options_init(opts, version);
513             }
514              
515 6           int git_worktree_is_prunable(git_worktree *wt,
516             git_worktree_prune_options *opts)
517             {
518 6           git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
519              
520 6 50         GIT_ERROR_CHECK_VERSION(
521             opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION,
522             "git_worktree_prune_options");
523              
524 6 50         if (opts)
525 6           memcpy(&popts, opts, sizeof(popts));
526              
527 6 100         if ((popts.flags & GIT_WORKTREE_PRUNE_LOCKED) == 0) {
528 3           git_buf reason = GIT_BUF_INIT;
529             int error;
530              
531 3 50         if ((error = git_worktree_is_locked(&reason, wt)) < 0)
532 1           return error;
533              
534 3 100         if (error) {
535 1 50         if (!reason.size)
536 0           git_buf_attach_notowned(&reason, "no reason given", 15);
537 1           git_error_set(GIT_ERROR_WORKTREE, "not pruning locked working tree: '%s'", reason.ptr);
538 1           git_buf_dispose(&reason);
539 3           return 0;
540             }
541             }
542              
543 6           if ((popts.flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
544 1           git_worktree_validate(wt) == 0) {
545 1           git_error_set(GIT_ERROR_WORKTREE, "not pruning valid working tree");
546 1           return 0;
547             }
548              
549 6           return 1;
550             }
551              
552 2           int git_worktree_prune(git_worktree *wt,
553             git_worktree_prune_options *opts)
554             {
555 2           git_worktree_prune_options popts = GIT_WORKTREE_PRUNE_OPTIONS_INIT;
556 2           git_buf path = GIT_BUF_INIT;
557             char *wtpath;
558             int err;
559              
560 2 50         GIT_ERROR_CHECK_VERSION(
561             opts, GIT_WORKTREE_PRUNE_OPTIONS_VERSION,
562             "git_worktree_prune_options");
563              
564 2 50         if (opts)
565 2           memcpy(&popts, opts, sizeof(popts));
566              
567 2 50         if (!git_worktree_is_prunable(wt, &popts)) {
568 0           err = -1;
569 0           goto out;
570             }
571              
572             /* Delete gitdir in parent repository */
573 2 50         if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->commondir_path, wt->name)) < 0)
574 0           goto out;
575 2 50         if (!git_path_exists(path.ptr))
576             {
577 0           git_error_set(GIT_ERROR_WORKTREE, "worktree gitdir '%s' does not exist", path.ptr);
578 0           err = -1;
579 0           goto out;
580             }
581 2 50         if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
582 0           goto out;
583              
584             /* Skip deletion of the actual working tree if it does
585             * not exist or deletion was not requested */
586 4           if ((popts.flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
587 2           !git_path_exists(wt->gitlink_path))
588             {
589             goto out;
590             }
591              
592 2 50         if ((wtpath = git_path_dirname(wt->gitlink_path)) == NULL)
593 0           goto out;
594 2           git_buf_attach(&path, wtpath, 0);
595 2 50         if (!git_path_exists(path.ptr))
596             {
597 0           git_error_set(GIT_ERROR_WORKTREE, "working tree '%s' does not exist", path.ptr);
598 0           err = -1;
599 0           goto out;
600             }
601 2 50         if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
602 0           goto out;
603              
604             out:
605 2           git_buf_dispose(&path);
606              
607 2           return err;
608             }