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