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