]> git.proxmox.com Git - libgit2.git/blob - src/worktree.c
7977d8e7bed7858190b3aadba02ac18d6bb2f12f
[libgit2.git] / src / worktree.c
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 "common.h"
9
10 #include "git2/branch.h"
11 #include "git2/commit.h"
12 #include "git2/worktree.h"
13
14 #include "repository.h"
15 #include "worktree.h"
16
17 static bool is_worktree_dir(const char *dir)
18 {
19 git_buf buf = GIT_BUF_INIT;
20 int error;
21
22 if (git_buf_sets(&buf, dir) < 0)
23 return -1;
24
25 error = git_path_contains_file(&buf, "commondir")
26 && git_path_contains_file(&buf, "gitdir")
27 && git_path_contains_file(&buf, "HEAD");
28
29 git_buf_free(&buf);
30 return error;
31 }
32
33 int git_worktree_list(git_strarray *wts, git_repository *repo)
34 {
35 git_vector worktrees = GIT_VECTOR_INIT;
36 git_buf path = GIT_BUF_INIT;
37 char *worktree;
38 unsigned i, len;
39 int error;
40
41 assert(wts && repo);
42
43 wts->count = 0;
44 wts->strings = NULL;
45
46 if ((error = git_buf_printf(&path, "%s/worktrees/", repo->commondir)) < 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_free(&path);
69
70 return error;
71 }
72
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 assert(base && file);
78
79 if (git_buf_joinpath(&path, base, file) < 0)
80 goto err;
81 if (git_futils_readbuffer(&buf, path.ptr) < 0)
82 goto err;
83 git_buf_free(&path);
84
85 git_buf_rtrim(&buf);
86
87 if (!git_path_is_relative(buf.ptr))
88 return git_buf_detach(&buf);
89
90 if (git_buf_sets(&path, base) < 0)
91 goto err;
92 if (git_path_apply_relative(&path, buf.ptr) < 0)
93 goto err;
94 git_buf_free(&buf);
95
96 return git_buf_detach(&path);
97
98 err:
99 git_buf_free(&buf);
100 git_buf_free(&path);
101
102 return NULL;
103 }
104
105 static int write_wtfile(const char *base, const char *file, const git_buf *buf)
106 {
107 git_buf path = GIT_BUF_INIT;
108 int err;
109
110 assert(base && file && buf);
111
112 if ((err = git_buf_joinpath(&path, base, file)) < 0)
113 goto out;
114
115 if ((err = git_futils_writebuffer(buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
116 goto out;
117
118 out:
119 git_buf_free(&path);
120
121 return err;
122 }
123
124 int git_worktree_lookup(git_worktree **out, git_repository *repo, const char *name)
125 {
126 git_buf path = GIT_BUF_INIT;
127 git_worktree *wt = NULL;
128 int error;
129
130 assert(repo && name);
131
132 *out = NULL;
133
134 if ((error = git_buf_printf(&path, "%s/worktrees/%s", repo->commondir, name)) < 0)
135 goto out;
136
137 if (!is_worktree_dir(path.ptr)) {
138 error = -1;
139 goto out;
140 }
141
142 if ((wt = git__malloc(sizeof(struct git_repository))) == NULL) {
143 error = -1;
144 goto out;
145 }
146
147 if ((wt->name = git__strdup(name)) == NULL
148 || (wt->commondir_path = git_worktree__read_link(path.ptr, "commondir")) == NULL
149 || (wt->gitlink_path = git_worktree__read_link(path.ptr, "gitdir")) == NULL
150 || (wt->parent_path = git__strdup(git_repository_path(repo))) == NULL) {
151 error = -1;
152 goto out;
153 }
154 wt->gitdir_path = git_buf_detach(&path);
155 wt->locked = !!git_worktree_is_locked(NULL, wt);
156
157 (*out) = wt;
158
159 out:
160 git_buf_free(&path);
161
162 if (error)
163 git_worktree_free(wt);
164
165 return error;
166 }
167
168 void git_worktree_free(git_worktree *wt)
169 {
170 if (!wt)
171 return;
172
173 git__free(wt->commondir_path);
174 git__free(wt->gitlink_path);
175 git__free(wt->gitdir_path);
176 git__free(wt->parent_path);
177 git__free(wt->name);
178 git__free(wt);
179 }
180
181 int git_worktree_validate(const git_worktree *wt)
182 {
183 git_buf buf = GIT_BUF_INIT;
184 int err = 0;
185
186 assert(wt);
187
188 git_buf_puts(&buf, wt->gitdir_path);
189 if (!is_worktree_dir(buf.ptr)) {
190 giterr_set(GITERR_WORKTREE,
191 "Worktree gitdir ('%s') is not valid",
192 wt->gitlink_path);
193 err = -1;
194 goto out;
195 }
196
197 if (!git_path_exists(wt->parent_path)) {
198 giterr_set(GITERR_WORKTREE,
199 "Worktree parent directory ('%s') does not exist ",
200 wt->parent_path);
201 err = -2;
202 goto out;
203 }
204
205 if (!git_path_exists(wt->commondir_path)) {
206 giterr_set(GITERR_WORKTREE,
207 "Worktree common directory ('%s') does not exist ",
208 wt->commondir_path);
209 err = -3;
210 goto out;
211 }
212
213 out:
214 git_buf_free(&buf);
215
216 return err;
217 }
218
219 int git_worktree_add(git_worktree **out, git_repository *repo, const char *name, const char *worktree)
220 {
221 git_buf path = GIT_BUF_INIT, buf = GIT_BUF_INIT;
222 git_reference *ref = NULL, *head = NULL;
223 git_commit *commit = NULL;
224 git_repository *wt = NULL;
225 git_checkout_options coopts = GIT_CHECKOUT_OPTIONS_INIT;
226 int err;
227
228 assert(out && repo && name && worktree);
229
230 *out = NULL;
231
232 /* Create worktree related files in commondir */
233 if ((err = git_buf_joinpath(&path, repo->commondir, "worktrees")) < 0)
234 goto out;
235 if (!git_path_exists(path.ptr))
236 if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
237 goto out;
238 if ((err = git_buf_joinpath(&path, path.ptr, name)) < 0)
239 goto out;
240 if ((err = git_futils_mkdir(path.ptr, 0755, GIT_MKDIR_EXCL)) < 0)
241 goto out;
242
243 /* Create worktree work dir */
244 if ((err = git_futils_mkdir(worktree, 0755, GIT_MKDIR_EXCL)) < 0)
245 goto out;
246
247 /* Create worktree .git file */
248 if ((err = git_buf_printf(&buf, "gitdir: %s\n", path.ptr)) < 0)
249 goto out;
250 if ((err = write_wtfile(worktree, ".git", &buf)) < 0)
251 goto out;
252
253 /* Create commondir files */
254 if ((err = git_buf_sets(&buf, repo->commondir)) < 0
255 || (err = git_buf_putc(&buf, '\n')) < 0
256 || (err = write_wtfile(path.ptr, "commondir", &buf)) < 0)
257 goto out;
258 if ((err = git_buf_joinpath(&buf, worktree, ".git")) < 0
259 || (err = git_buf_putc(&buf, '\n')) < 0
260 || (err = write_wtfile(path.ptr, "gitdir", &buf)) < 0)
261 goto out;
262
263 /* Create new branch */
264 if ((err = git_repository_head(&head, repo)) < 0)
265 goto out;
266 if ((err = git_commit_lookup(&commit, repo, &head->target.oid)) < 0)
267 goto out;
268 if ((err = git_branch_create(&ref, repo, name, commit, false)) < 0)
269 goto out;
270
271 /* Set worktree's HEAD */
272 if ((err = git_repository_create_head(path.ptr, name)) < 0)
273 goto out;
274 if ((err = git_repository_open(&wt, worktree)) < 0)
275 goto out;
276
277 /* Checkout worktree's HEAD */
278 coopts.checkout_strategy = GIT_CHECKOUT_FORCE;
279 if ((err = git_checkout_head(wt, &coopts)) < 0)
280 goto out;
281
282 /* Load result */
283 if ((err = git_worktree_lookup(out, repo, name)) < 0)
284 goto out;
285
286 out:
287 git_buf_free(&path);
288 git_buf_free(&buf);
289 git_reference_free(ref);
290 git_reference_free(head);
291 git_commit_free(commit);
292 git_repository_free(wt);
293
294 return err;
295 }
296
297 int git_worktree_lock(git_worktree *wt, char *creason)
298 {
299 git_buf buf = GIT_BUF_INIT, path = GIT_BUF_INIT;
300 int err;
301
302 assert(wt);
303
304 if ((err = git_worktree_is_locked(NULL, wt)) < 0)
305 goto out;
306
307 if ((err = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0)
308 goto out;
309
310 if (creason)
311 git_buf_attach_notowned(&buf, creason, strlen(creason));
312
313 if ((err = git_futils_writebuffer(&buf, path.ptr, O_CREAT|O_EXCL|O_WRONLY, 0644)) < 0)
314 goto out;
315
316 wt->locked = 1;
317
318 out:
319 git_buf_free(&path);
320
321 return err;
322 }
323
324 int git_worktree_unlock(git_worktree *wt)
325 {
326 git_buf path = GIT_BUF_INIT;
327
328 assert(wt);
329
330 if (!git_worktree_is_locked(NULL, wt))
331 return 0;
332
333 if (git_buf_joinpath(&path, wt->gitdir_path, "locked") < 0)
334 return -1;
335
336 if (p_unlink(path.ptr) != 0) {
337 git_buf_free(&path);
338 return -1;
339 }
340
341 wt->locked = 0;
342
343 git_buf_free(&path);
344
345 return 0;
346 }
347
348 int git_worktree_is_locked(git_buf *reason, const git_worktree *wt)
349 {
350 git_buf path = GIT_BUF_INIT;
351 int ret;
352
353 assert(wt);
354
355 if (reason)
356 git_buf_clear(reason);
357
358 if ((ret = git_buf_joinpath(&path, wt->gitdir_path, "locked")) < 0)
359 goto out;
360 if ((ret = git_path_exists(path.ptr)) && reason)
361 git_futils_readbuffer(reason, path.ptr);
362
363 out:
364 git_buf_free(&path);
365
366 return ret;
367 }
368
369 int git_worktree_is_prunable(git_worktree *wt, unsigned flags)
370 {
371 git_buf reason = GIT_BUF_INIT;
372
373 if ((flags & GIT_WORKTREE_PRUNE_LOCKED) == 0 &&
374 git_worktree_is_locked(&reason, wt))
375 {
376 if (!reason.size)
377 git_buf_attach_notowned(&reason, "no reason given", 15);
378 giterr_set(GITERR_WORKTREE, "Not pruning locked working tree: '%s'", reason.ptr);
379 git_buf_free(&reason);
380
381 return 0;
382 }
383
384 if ((flags & GIT_WORKTREE_PRUNE_VALID) == 0 &&
385 git_worktree_validate(wt) == 0)
386 {
387 giterr_set(GITERR_WORKTREE, "Not pruning valid working tree");
388 return 0;
389 }
390
391 return 1;
392 }
393
394 int git_worktree_prune(git_worktree *wt, unsigned flags)
395 {
396 git_buf path = GIT_BUF_INIT;
397 char *wtpath;
398 int err;
399
400 if (!git_worktree_is_prunable(wt, flags)) {
401 err = -1;
402 goto out;
403 }
404
405 /* Delete gitdir in parent repository */
406 if ((err = git_buf_printf(&path, "%s/worktrees/%s", wt->parent_path, wt->name)) < 0)
407 goto out;
408 if (!git_path_exists(path.ptr))
409 {
410 giterr_set(GITERR_WORKTREE, "Worktree gitdir '%s' does not exist", path.ptr);
411 err = -1;
412 goto out;
413 }
414 if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
415 goto out;
416
417 /* Skip deletion of the actual working tree if it does
418 * not exist or deletion was not requested */
419 if ((flags & GIT_WORKTREE_PRUNE_WORKING_TREE) == 0 ||
420 !git_path_exists(wt->gitlink_path))
421 {
422 goto out;
423 }
424
425 if ((wtpath = git_path_dirname(wt->gitlink_path)) == NULL)
426 goto out;
427 git_buf_attach(&path, wtpath, 0);
428 if (!git_path_exists(path.ptr))
429 {
430 giterr_set(GITERR_WORKTREE, "Working tree '%s' does not exist", path.ptr);
431 err = -1;
432 goto out;
433 }
434 if ((err = git_futils_rmdir_r(path.ptr, NULL, GIT_RMDIR_REMOVE_FILES)) < 0)
435 goto out;
436
437 out:
438 git_buf_free(&path);
439
440 return err;
441 }