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