blob: 257ba4cf1ee5b71de8be0bff85511415f6600347 [file] [log] [blame]
Michael Rappazzoac6c5612015-10-02 07:55:31 -04001#include "cache.h"
Brandon Williamsb3371722017-06-22 11:43:37 -07002#include "repository.h"
Michael Rappazzoac6c5612015-10-02 07:55:31 -04003#include "refs.h"
4#include "strbuf.h"
5#include "worktree.h"
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +07006#include "dir.h"
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 20:01:33 +07007#include "wt-status.h"
Derrick Stolee615a84a2022-02-07 21:32:59 +00008#include "config.h"
Michael Rappazzoac6c5612015-10-02 07:55:31 -04009
Michael Rappazzo51934902015-10-08 13:01:03 -040010void free_worktrees(struct worktree **worktrees)
11{
12 int i = 0;
13
14 for (i = 0; worktrees[i]; i++) {
15 free(worktrees[i]->path);
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 20:01:26 +070016 free(worktrees[i]->id);
Michael Rappazzo92718b72015-10-08 13:01:04 -040017 free(worktrees[i]->head_ref);
Nguyễn Thái Ngọc Duy346ef532016-06-13 19:18:23 +070018 free(worktrees[i]->lock_reason);
Rafael Silvafc0c7d52021-01-19 22:27:34 +010019 free(worktrees[i]->prune_reason);
Michael Rappazzo51934902015-10-08 13:01:03 -040020 free(worktrees[i]);
21 }
22 free (worktrees);
23}
24
Michael Rappazzo51934902015-10-08 13:01:03 -040025/**
Martin Ågrencfaf9f02020-09-27 15:15:45 +020026 * Update head_oid, head_ref and is_detached of the given worktree
Michael Rappazzo92718b72015-10-08 13:01:04 -040027 */
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +070028static void add_head_info(struct worktree *wt)
Michael Rappazzo92718b72015-10-08 13:01:04 -040029{
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +070030 int flags;
31 const char *target;
32
Ævar Arnfjörð Bjarmasonf1da24c2021-10-16 11:39:27 +020033 target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +070034 "HEAD",
Nguyễn Thái Ngọc Duy31824d12017-08-24 17:41:24 +070035 0,
Ævar Arnfjörð Bjarmasonce14de02022-01-26 15:37:01 +010036 &wt->head_oid, &flags);
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +070037 if (!target)
38 return;
39
40 if (flags & REF_ISSYMREF)
41 wt->head_ref = xstrdup(target);
42 else
43 wt->is_detached = 1;
Michael Rappazzo92718b72015-10-08 13:01:04 -040044}
45
46/**
Michael Rappazzo51934902015-10-08 13:01:03 -040047 * get the main worktree
48 */
49static struct worktree *get_main_worktree(void)
Michael Rappazzo1ceb7f92015-10-08 13:01:02 -040050{
Michael Rappazzo51934902015-10-08 13:01:03 -040051 struct worktree *worktree = NULL;
Michael Rappazzo51934902015-10-08 13:01:03 -040052 struct strbuf worktree_path = STRBUF_INIT;
Michael Rappazzo1ceb7f92015-10-08 13:01:02 -040053
Eric Sunshine918d8ff2020-07-31 19:32:14 -040054 strbuf_add_real_path(&worktree_path, get_git_common_dir());
55 strbuf_strip_suffix(&worktree_path, "/.git");
Michael Rappazzo51934902015-10-08 13:01:03 -040056
René Scharfeca56dad2021-03-13 17:17:22 +010057 CALLOC_ARRAY(worktree, 1);
Michael Rappazzo92718b72015-10-08 13:01:04 -040058 worktree->path = strbuf_detach(&worktree_path, NULL);
Jonathan Tanf3534c92019-04-19 10:21:28 -070059 /*
60 * NEEDSWORK: If this function is called from a secondary worktree and
61 * config.worktree is present, is_bare_repository_cfg will reflect the
62 * contents of config.worktree, not the contents of the main worktree.
63 * This means that worktree->is_bare may be set to 0 even if the main
64 * worktree is configured to be bare.
65 */
66 worktree->is_bare = (is_bare_repository_cfg == 1) ||
67 is_bare_repository();
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +070068 add_head_info(worktree);
Michael Rappazzo51934902015-10-08 13:01:03 -040069 return worktree;
Michael Rappazzo1ceb7f92015-10-08 13:01:02 -040070}
71
Michael Rappazzo51934902015-10-08 13:01:03 -040072static struct worktree *get_linked_worktree(const char *id)
Michael Rappazzoac6c5612015-10-02 07:55:31 -040073{
Michael Rappazzo51934902015-10-08 13:01:03 -040074 struct worktree *worktree = NULL;
Michael Rappazzoac6c5612015-10-02 07:55:31 -040075 struct strbuf path = STRBUF_INIT;
Michael Rappazzo51934902015-10-08 13:01:03 -040076 struct strbuf worktree_path = STRBUF_INIT;
Michael Rappazzoac6c5612015-10-02 07:55:31 -040077
Michael Rappazzo1ceb7f92015-10-08 13:01:02 -040078 if (!id)
79 die("Missing linked worktree name");
Michael Rappazzoac6c5612015-10-02 07:55:31 -040080
Brandon Williamsb3371722017-06-22 11:43:37 -070081 strbuf_git_common_path(&path, the_repository, "worktrees/%s/gitdir", id);
Michael Rappazzo51934902015-10-08 13:01:03 -040082 if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
83 /* invalid gitdir file */
84 goto done;
Michael Rappazzo51934902015-10-08 13:01:03 -040085 strbuf_rtrim(&worktree_path);
Eric Sunshine1c4854e2020-07-31 19:32:13 -040086 strbuf_strip_suffix(&worktree_path, "/.git");
Michael Rappazzo51934902015-10-08 13:01:03 -040087
René Scharfeca56dad2021-03-13 17:17:22 +010088 CALLOC_ARRAY(worktree, 1);
Michael Rappazzo92718b72015-10-08 13:01:04 -040089 worktree->path = strbuf_detach(&worktree_path, NULL);
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 20:01:26 +070090 worktree->id = xstrdup(id);
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +070091 add_head_info(worktree);
Michael Rappazzo51934902015-10-08 13:01:03 -040092
Michael Rappazzoac6c5612015-10-02 07:55:31 -040093done:
94 strbuf_release(&path);
Michael Rappazzo51934902015-10-08 13:01:03 -040095 strbuf_release(&worktree_path);
Michael Rappazzo51934902015-10-08 13:01:03 -040096 return worktree;
Michael Rappazzoac6c5612015-10-02 07:55:31 -040097}
98
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +070099static void mark_current_worktree(struct worktree **worktrees)
100{
René Scharfe0aaad412017-01-26 18:54:23 +0100101 char *git_dir = absolute_pathdup(get_git_dir());
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +0700102 int i;
103
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +0700104 for (i = 0; worktrees[i]; i++) {
105 struct worktree *wt = worktrees[i];
Nguyễn Thái Ngọc Duy360af2d2016-05-22 16:33:52 +0700106 const char *wt_git_dir = get_worktree_git_dir(wt);
107
108 if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {
109 wt->is_current = 1;
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +0700110 break;
Nguyễn Thái Ngọc Duy360af2d2016-05-22 16:33:52 +0700111 }
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +0700112 }
Nguyễn Thái Ngọc Duy360af2d2016-05-22 16:33:52 +0700113 free(git_dir);
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +0700114}
115
Eric Sunshine03f24652020-06-19 19:35:44 -0400116struct worktree **get_worktrees(void)
Michael Rappazzoac6c5612015-10-02 07:55:31 -0400117{
Michael Rappazzo51934902015-10-08 13:01:03 -0400118 struct worktree **list = NULL;
Michael Rappazzoac6c5612015-10-02 07:55:31 -0400119 struct strbuf path = STRBUF_INIT;
120 DIR *dir;
121 struct dirent *d;
Michael Rappazzo51934902015-10-08 13:01:03 -0400122 int counter = 0, alloc = 2;
Michael Rappazzoac6c5612015-10-02 07:55:31 -0400123
René Scharfe3f646992017-02-25 11:30:03 +0100124 ALLOC_ARRAY(list, alloc);
Michael Rappazzo51934902015-10-08 13:01:03 -0400125
Nguyễn Thái Ngọc Duya2345632016-11-28 16:36:54 +0700126 list[counter++] = get_main_worktree();
Michael Rappazzoac6c5612015-10-02 07:55:31 -0400127
128 strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
129 dir = opendir(path.buf);
130 strbuf_release(&path);
Michael Rappazzo51934902015-10-08 13:01:03 -0400131 if (dir) {
Elijah Newrenb548f0f2021-05-12 17:28:22 +0000132 while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
Michael Rappazzo51934902015-10-08 13:01:03 -0400133 struct worktree *linked = NULL;
Michael Rappazzoac6c5612015-10-02 07:55:31 -0400134
Nguyễn Thái Ngọc Duyd4cddd62016-01-18 18:21:29 +0700135 if ((linked = get_linked_worktree(d->d_name))) {
136 ALLOC_GROW(list, counter + 1, alloc);
137 list[counter++] = linked;
138 }
Michael Rappazzo51934902015-10-08 13:01:03 -0400139 }
140 closedir(dir);
Michael Rappazzoac6c5612015-10-02 07:55:31 -0400141 }
Michael Rappazzo51934902015-10-08 13:01:03 -0400142 ALLOC_GROW(list, counter + 1, alloc);
143 list[counter] = NULL;
Nguyễn Thái Ngọc Duy750e8a62016-04-22 20:01:28 +0700144
145 mark_current_worktree(list);
Michael Rappazzo51934902015-10-08 13:01:03 -0400146 return list;
147}
148
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 20:01:26 +0700149const char *get_worktree_git_dir(const struct worktree *wt)
Michael Rappazzo51934902015-10-08 13:01:03 -0400150{
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 20:01:26 +0700151 if (!wt)
152 return get_git_dir();
153 else if (!wt->id)
154 return get_git_common_dir();
155 else
156 return git_common_path("worktrees/%s", wt->id);
157}
158
Nguyễn Thái Ngọc Duy080739b2016-06-13 19:18:26 +0700159static struct worktree *find_worktree_by_suffix(struct worktree **list,
160 const char *suffix)
161{
162 struct worktree *found = NULL;
163 int nr_found = 0, suffixlen;
164
165 suffixlen = strlen(suffix);
166 if (!suffixlen)
167 return NULL;
168
169 for (; *list && nr_found < 2; list++) {
170 const char *path = (*list)->path;
171 int pathlen = strlen(path);
172 int start = pathlen - suffixlen;
173
174 /* suffix must start at directory boundary */
175 if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&
176 !fspathcmp(suffix, path + start)) {
177 found = *list;
178 nr_found++;
179 }
180 }
181 return nr_found == 1 ? found : NULL;
182}
183
Nguyễn Thái Ngọc Duy68353142016-06-03 19:19:39 +0700184struct worktree *find_worktree(struct worktree **list,
185 const char *prefix,
186 const char *arg)
187{
Nguyễn Thái Ngọc Duy080739b2016-06-13 19:18:26 +0700188 struct worktree *wt;
Jeff Kinge4da43b2017-03-20 21:28:49 -0400189 char *to_free = NULL;
Nguyễn Thái Ngọc Duy68353142016-06-03 19:19:39 +0700190
Nguyễn Thái Ngọc Duy080739b2016-06-13 19:18:26 +0700191 if ((wt = find_worktree_by_suffix(list, arg)))
192 return wt;
193
Jeff Kinge4da43b2017-03-20 21:28:49 -0400194 if (prefix)
195 arg = to_free = prefix_filename(prefix, arg);
Eric Sunshinebb4995f2020-02-24 04:08:47 -0500196 wt = find_worktree_by_path(list, arg);
197 free(to_free);
198 return wt;
199}
200
201struct worktree *find_worktree_by_path(struct worktree **list, const char *p)
202{
Alexandr Miloslavskiy4530a852020-03-10 13:11:23 +0000203 struct strbuf wt_path = STRBUF_INIT;
Eric Sunshinebb4995f2020-02-24 04:08:47 -0500204 char *path = real_pathdup(p, 0);
205
206 if (!path)
Eric Sunshine4c5fa9e2018-08-28 17:20:18 -0400207 return NULL;
Nguyễn Thái Ngọc Duy105df732019-05-13 17:49:44 +0700208 for (; *list; list++) {
Alexandr Miloslavskiy4530a852020-03-10 13:11:23 +0000209 if (!strbuf_realpath(&wt_path, (*list)->path, 0))
210 continue;
Nguyễn Thái Ngọc Duy105df732019-05-13 17:49:44 +0700211
Alexandr Miloslavskiy4530a852020-03-10 13:11:23 +0000212 if (!fspathcmp(path, wt_path.buf))
Nguyễn Thái Ngọc Duy68353142016-06-03 19:19:39 +0700213 break;
Nguyễn Thái Ngọc Duy105df732019-05-13 17:49:44 +0700214 }
Nguyễn Thái Ngọc Duy68353142016-06-03 19:19:39 +0700215 free(path);
Alexandr Miloslavskiy4530a852020-03-10 13:11:23 +0000216 strbuf_release(&wt_path);
Nguyễn Thái Ngọc Duy68353142016-06-03 19:19:39 +0700217 return *list;
218}
219
Nguyễn Thái Ngọc Duy984ad9e2016-06-03 19:19:40 +0700220int is_main_worktree(const struct worktree *wt)
221{
222 return !wt->id;
223}
224
Nickolai Belakovskid236f122018-10-29 23:24:09 -0700225const char *worktree_lock_reason(struct worktree *wt)
Nguyễn Thái Ngọc Duy346ef532016-06-13 19:18:23 +0700226{
Rafael Silvaeb361352021-01-19 22:27:35 +0100227 if (is_main_worktree(wt))
228 return NULL;
Nguyễn Thái Ngọc Duy346ef532016-06-13 19:18:23 +0700229
230 if (!wt->lock_reason_valid) {
231 struct strbuf path = STRBUF_INIT;
232
233 strbuf_addstr(&path, worktree_git_path(wt, "locked"));
234 if (file_exists(path.buf)) {
235 struct strbuf lock_reason = STRBUF_INIT;
236 if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
237 die_errno(_("failed to read '%s'"), path.buf);
238 strbuf_trim(&lock_reason);
239 wt->lock_reason = strbuf_detach(&lock_reason, NULL);
240 } else
241 wt->lock_reason = NULL;
242 wt->lock_reason_valid = 1;
243 strbuf_release(&path);
244 }
245
246 return wt->lock_reason;
247}
248
Rafael Silvafc0c7d52021-01-19 22:27:34 +0100249const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire)
250{
251 struct strbuf reason = STRBUF_INIT;
252 char *path = NULL;
253
254 if (is_main_worktree(wt))
255 return NULL;
256 if (wt->prune_reason_valid)
257 return wt->prune_reason;
258
259 if (should_prune_worktree(wt->id, &reason, &path, expire))
260 wt->prune_reason = strbuf_detach(&reason, NULL);
261 wt->prune_reason_valid = 1;
262
263 strbuf_release(&reason);
264 free(path);
265 return wt->prune_reason;
266}
267
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 16:53:51 +0700268/* convenient wrapper to deal with NULL strbuf */
Ævar Arnfjörð Bjarmason48ca53c2021-07-13 10:05:18 +0200269__attribute__((format (printf, 2, 3)))
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 16:53:51 +0700270static void strbuf_addf_gently(struct strbuf *buf, const char *fmt, ...)
271{
272 va_list params;
273
274 if (!buf)
275 return;
276
277 va_start(params, fmt);
278 strbuf_vaddf(buf, fmt, params);
279 va_end(params);
280}
281
Nguyễn Thái Ngọc Duyee6763a2018-02-12 16:49:40 +0700282int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
283 unsigned flags)
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 16:53:51 +0700284{
285 struct strbuf wt_path = STRBUF_INIT;
Alexandr Miloslavskiy3d7747e2020-03-10 13:11:22 +0000286 struct strbuf realpath = STRBUF_INIT;
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 16:53:51 +0700287 char *path = NULL;
288 int err, ret = -1;
289
290 strbuf_addf(&wt_path, "%s/.git", wt->path);
291
292 if (is_main_worktree(wt)) {
293 if (is_directory(wt_path.buf)) {
294 ret = 0;
295 goto done;
296 }
297 /*
298 * Main worktree using .git file to point to the
299 * repository would make it impossible to know where
300 * the actual worktree is if this function is executed
301 * from another worktree. No .git file support for now.
302 */
303 strbuf_addf_gently(errmsg,
304 _("'%s' at main working tree is not the repository directory"),
305 wt_path.buf);
306 goto done;
307 }
308
309 /*
310 * Make sure "gitdir" file points to a real .git file and that
311 * file points back here.
312 */
313 if (!is_absolute_path(wt->path)) {
314 strbuf_addf_gently(errmsg,
315 _("'%s' file does not contain absolute path to the working tree location"),
316 git_common_path("worktrees/%s/gitdir", wt->id));
317 goto done;
318 }
319
Nguyễn Thái Ngọc Duyee6763a2018-02-12 16:49:40 +0700320 if (flags & WT_VALIDATE_WORKTREE_MISSING_OK &&
321 !file_exists(wt->path)) {
322 ret = 0;
323 goto done;
324 }
325
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 16:53:51 +0700326 if (!file_exists(wt_path.buf)) {
327 strbuf_addf_gently(errmsg, _("'%s' does not exist"), wt_path.buf);
328 goto done;
329 }
330
331 path = xstrdup_or_null(read_gitfile_gently(wt_path.buf, &err));
332 if (!path) {
333 strbuf_addf_gently(errmsg, _("'%s' is not a .git file, error code %d"),
334 wt_path.buf, err);
335 goto done;
336 }
337
Alexandr Miloslavskiy3d7747e2020-03-10 13:11:22 +0000338 strbuf_realpath(&realpath, git_common_path("worktrees/%s", wt->id), 1);
339 ret = fspathcmp(path, realpath.buf);
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 16:53:51 +0700340
341 if (ret)
342 strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
343 wt->path, git_common_path("worktrees/%s", wt->id));
344done:
345 free(path);
346 strbuf_release(&wt_path);
Alexandr Miloslavskiy3d7747e2020-03-10 13:11:22 +0000347 strbuf_release(&realpath);
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 16:53:51 +0700348 return ret;
349}
350
Nguyễn Thái Ngọc Duy9c620fc2018-02-12 16:49:35 +0700351void update_worktree_location(struct worktree *wt, const char *path_)
352{
353 struct strbuf path = STRBUF_INIT;
354
355 if (is_main_worktree(wt))
Johannes Schindelin033abf92018-05-02 11:38:39 +0200356 BUG("can't relocate main worktree");
Nguyễn Thái Ngọc Duy9c620fc2018-02-12 16:49:35 +0700357
358 strbuf_realpath(&path, path_, 1);
359 if (fspathcmp(wt->path, path.buf)) {
360 write_file(git_common_path("worktrees/%s/gitdir", wt->id),
361 "%s/.git", path.buf);
362 free(wt->path);
363 wt->path = strbuf_detach(&path, NULL);
364 }
365 strbuf_release(&path);
366}
367
Nguyễn Thái Ngọc Duy14ace5b2016-04-22 20:01:36 +0700368int is_worktree_being_rebased(const struct worktree *wt,
369 const char *target)
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 20:01:33 +0700370{
371 struct wt_status_state state;
372 int found_rebase;
373
374 memset(&state, 0, sizeof(state));
375 found_rebase = wt_status_check_rebase(wt, &state) &&
Martin Ågrena46d1f72020-09-27 15:15:47 +0200376 (state.rebase_in_progress ||
377 state.rebase_interactive_in_progress) &&
378 state.branch &&
379 skip_prefix(target, "refs/heads/", &target) &&
380 !strcmp(state.branch, target);
Martin Ågren962dd7e2020-09-27 15:15:43 +0200381 wt_status_state_free_buffers(&state);
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 20:01:33 +0700382 return found_rebase;
383}
384
Nguyễn Thái Ngọc Duy14ace5b2016-04-22 20:01:36 +0700385int is_worktree_being_bisected(const struct worktree *wt,
386 const char *target)
Nguyễn Thái Ngọc Duy04a3dfb2016-04-22 20:01:35 +0700387{
388 struct wt_status_state state;
Martin Ågrenfb07bd42020-09-27 15:15:46 +0200389 int found_bisect;
Nguyễn Thái Ngọc Duy04a3dfb2016-04-22 20:01:35 +0700390
391 memset(&state, 0, sizeof(state));
Martin Ågrenfb07bd42020-09-27 15:15:46 +0200392 found_bisect = wt_status_check_bisect(wt, &state) &&
393 state.branch &&
Martin Ågrena46d1f72020-09-27 15:15:47 +0200394 skip_prefix(target, "refs/heads/", &target) &&
395 !strcmp(state.branch, target);
Martin Ågren962dd7e2020-09-27 15:15:43 +0200396 wt_status_state_free_buffers(&state);
Martin Ågrenfb07bd42020-09-27 15:15:46 +0200397 return found_bisect;
Nguyễn Thái Ngọc Duy04a3dfb2016-04-22 20:01:35 +0700398}
399
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 20:01:33 +0700400/*
401 * note: this function should be able to detect shared symref even if
402 * HEAD is temporarily detached (e.g. in the middle of rebase or
403 * bisect). New commands that do similar things should update this
404 * function as well.
405 */
Anders Kaseorgc8dd4912021-12-01 14:15:43 -0800406const struct worktree *find_shared_symref(struct worktree **worktrees,
407 const char *symref,
Nguyễn Thái Ngọc Duyd3b9ac02016-04-22 20:01:27 +0700408 const char *target)
Michael Rappazzo51934902015-10-08 13:01:03 -0400409{
Nguyễn Thái Ngọc Duyd3b9ac02016-04-22 20:01:27 +0700410 const struct worktree *existing = NULL;
Michael Rappazzo51934902015-10-08 13:01:03 -0400411 int i = 0;
412
413 for (i = 0; worktrees[i]; i++) {
Nguyễn Thái Ngọc Duyc8717142016-04-22 20:01:32 +0700414 struct worktree *wt = worktrees[i];
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +0700415 const char *symref_target;
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +0700416 struct ref_store *refs;
417 int flags;
418
Dennis Kaarsemaker171c6462016-10-12 18:41:07 +0200419 if (wt->is_bare)
420 continue;
Nguyễn Thái Ngọc Duyc8717142016-04-22 20:01:32 +0700421
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 20:01:33 +0700422 if (wt->is_detached && !strcmp(symref, "HEAD")) {
423 if (is_worktree_being_rebased(wt, target)) {
424 existing = wt;
425 break;
426 }
Nguyễn Thái Ngọc Duy04a3dfb2016-04-22 20:01:35 +0700427 if (is_worktree_being_bisected(wt, target)) {
428 existing = wt;
429 break;
430 }
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 20:01:33 +0700431 }
432
Nguyễn Thái Ngọc Duyfa099d22017-04-24 17:01:23 +0700433 refs = get_worktree_ref_store(wt);
Ævar Arnfjörð Bjarmasonf1da24c2021-10-16 11:39:27 +0200434 symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
Ævar Arnfjörð Bjarmasonce14de02022-01-26 15:37:01 +0100435 NULL, &flags);
Jeff Kingdbd2b552017-10-19 13:49:36 -0400436 if ((flags & REF_ISSYMREF) &&
437 symref_target && !strcmp(symref_target, target)) {
Nguyễn Thái Ngọc Duyc8717142016-04-22 20:01:32 +0700438 existing = wt;
Michael Rappazzo51934902015-10-08 13:01:03 -0400439 break;
440 }
441 }
442
Michael Rappazzoac6c5612015-10-02 07:55:31 -0400443 return existing;
444}
Stefan Beller1a248cf2016-12-12 11:04:33 -0800445
446int submodule_uses_worktrees(const char *path)
447{
448 char *submodule_gitdir;
brian m. carlsone02a7142020-02-22 20:17:41 +0000449 struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
Stefan Beller1a248cf2016-12-12 11:04:33 -0800450 DIR *dir;
451 struct dirent *d;
Stefan Beller7c4be452016-12-27 09:50:13 -0800452 int ret = 0;
Martin Ågrene8805af2019-02-28 21:36:28 +0100453 struct repository_format format = REPOSITORY_FORMAT_INIT;
Stefan Beller1a248cf2016-12-12 11:04:33 -0800454
455 submodule_gitdir = git_pathdup_submodule(path, "%s", "");
456 if (!submodule_gitdir)
457 return 0;
458
459 /* The env would be set for the superproject. */
460 get_common_dir_noenv(&sb, submodule_gitdir);
Johannes Schindelind32de662017-05-04 15:59:19 +0200461 free(submodule_gitdir);
Stefan Beller1a248cf2016-12-12 11:04:33 -0800462
Stefan Beller1a248cf2016-12-12 11:04:33 -0800463 strbuf_addstr(&sb, "/config");
464 read_repository_format(&format, sb.buf);
brian m. carlsone02a7142020-02-22 20:17:41 +0000465 if (verify_repository_format(&format, &err)) {
466 strbuf_release(&err);
Stefan Beller1a248cf2016-12-12 11:04:33 -0800467 strbuf_release(&sb);
Martin Ågrene8805af2019-02-28 21:36:28 +0100468 clear_repository_format(&format);
Stefan Beller1a248cf2016-12-12 11:04:33 -0800469 return 1;
470 }
Martin Ågrene8805af2019-02-28 21:36:28 +0100471 clear_repository_format(&format);
brian m. carlsone02a7142020-02-22 20:17:41 +0000472 strbuf_release(&err);
Stefan Beller1a248cf2016-12-12 11:04:33 -0800473
474 /* Replace config by worktrees. */
475 strbuf_setlen(&sb, sb.len - strlen("config"));
476 strbuf_addstr(&sb, "worktrees");
477
478 /* See if there is any file inside the worktrees directory. */
479 dir = opendir(sb.buf);
480 strbuf_release(&sb);
Stefan Beller1a248cf2016-12-12 11:04:33 -0800481
482 if (!dir)
483 return 0;
484
Elijah Newrenb548f0f2021-05-12 17:28:22 +0000485 d = readdir_skip_dot_and_dotdot(dir);
Junio C Hamanoafe8a902022-05-02 09:50:37 -0700486 if (d)
Stefan Beller1a248cf2016-12-12 11:04:33 -0800487 ret = 1;
Stefan Beller1a248cf2016-12-12 11:04:33 -0800488 closedir(dir);
489 return ret;
490}
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 19:36:59 +0700491
Nguyễn Thái Ngọc Duy3a3b9d82018-10-21 10:08:54 +0200492int parse_worktree_ref(const char *worktree_ref, const char **name,
493 int *name_length, const char **ref)
494{
495 if (skip_prefix(worktree_ref, "main-worktree/", &worktree_ref)) {
496 if (!*worktree_ref)
497 return -1;
498 if (name)
499 *name = NULL;
500 if (name_length)
501 *name_length = 0;
502 if (ref)
503 *ref = worktree_ref;
504 return 0;
505 }
506 if (skip_prefix(worktree_ref, "worktrees/", &worktree_ref)) {
507 const char *slash = strchr(worktree_ref, '/');
508
509 if (!slash || slash == worktree_ref || !slash[1])
510 return -1;
511 if (name)
512 *name = worktree_ref;
513 if (name_length)
514 *name_length = slash - worktree_ref;
515 if (ref)
516 *ref = slash + 1;
517 return 0;
518 }
519 return -1;
520}
521
Nguyễn Thái Ngọc Duyab3e1f72018-10-21 10:08:56 +0200522void strbuf_worktree_ref(const struct worktree *wt,
523 struct strbuf *sb,
524 const char *refname)
525{
526 switch (ref_type(refname)) {
527 case REF_TYPE_PSEUDOREF:
528 case REF_TYPE_PER_WORKTREE:
529 if (wt && !wt->is_current) {
530 if (is_main_worktree(wt))
531 strbuf_addstr(sb, "main-worktree/");
532 else
533 strbuf_addf(sb, "worktrees/%s/", wt->id);
534 }
535 break;
536
537 case REF_TYPE_MAIN_PSEUDOREF:
538 case REF_TYPE_OTHER_PSEUDOREF:
539 break;
540
541 case REF_TYPE_NORMAL:
542 /*
543 * For shared refs, don't prefix worktrees/ or
544 * main-worktree/. It's not necessary and
545 * files-backend.c can't handle it anyway.
546 */
547 break;
548 }
549 strbuf_addstr(sb, refname);
550}
551
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 19:36:59 +0700552int other_head_refs(each_ref_fn fn, void *cb_data)
553{
554 struct worktree **worktrees, **p;
Martin Ågrenef2d5542020-09-27 15:15:44 +0200555 struct strbuf refname = STRBUF_INIT;
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 19:36:59 +0700556 int ret = 0;
557
Eric Sunshine03f24652020-06-19 19:35:44 -0400558 worktrees = get_worktrees();
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 19:36:59 +0700559 for (p = worktrees; *p; p++) {
560 struct worktree *wt = *p;
Nguyễn Thái Ngọc Duyab3e1f72018-10-21 10:08:56 +0200561 struct object_id oid;
562 int flag;
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 19:36:59 +0700563
564 if (wt->is_current)
565 continue;
566
Martin Ågrenef2d5542020-09-27 15:15:44 +0200567 strbuf_reset(&refname);
568 strbuf_worktree_ref(wt, &refname, "HEAD");
Ævar Arnfjörð Bjarmasonf1da24c2021-10-16 11:39:27 +0200569 if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
Ævar Arnfjörð Bjarmason76887df2021-10-16 11:39:14 +0200570 refname.buf,
571 RESOLVE_REF_READING,
Ævar Arnfjörð Bjarmasonce14de02022-01-26 15:37:01 +0100572 &oid, &flag))
Martin Ågrenef2d5542020-09-27 15:15:44 +0200573 ret = fn(refname.buf, &oid, flag, cb_data);
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 19:36:59 +0700574 if (ret)
575 break;
576 }
577 free_worktrees(worktrees);
Martin Ågrenef2d5542020-09-27 15:15:44 +0200578 strbuf_release(&refname);
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 19:36:59 +0700579 return ret;
580}
Eric Sunshinebdd1f3e2020-08-31 02:57:57 -0400581
582/*
583 * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
584 * pointing at <repo>/worktrees/<id>.
585 */
586static void repair_gitfile(struct worktree *wt,
587 worktree_repair_fn fn, void *cb_data)
588{
589 struct strbuf dotgit = STRBUF_INIT;
590 struct strbuf repo = STRBUF_INIT;
591 char *backlink;
592 const char *repair = NULL;
593 int err;
594
595 /* missing worktree can't be repaired */
596 if (!file_exists(wt->path))
597 return;
598
599 if (!is_directory(wt->path)) {
600 fn(1, wt->path, _("not a directory"), cb_data);
601 return;
602 }
603
604 strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
605 strbuf_addf(&dotgit, "%s/.git", wt->path);
606 backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
607
608 if (err == READ_GITFILE_ERR_NOT_A_FILE)
609 fn(1, wt->path, _(".git is not a file"), cb_data);
610 else if (err)
611 repair = _(".git file broken");
612 else if (fspathcmp(backlink, repo.buf))
613 repair = _(".git file incorrect");
614
615 if (repair) {
616 fn(0, wt->path, repair, cb_data);
617 write_file(dotgit.buf, "gitdir: %s", repo.buf);
618 }
619
620 free(backlink);
621 strbuf_release(&repo);
622 strbuf_release(&dotgit);
623}
624
625static void repair_noop(int iserr, const char *path, const char *msg,
626 void *cb_data)
627{
628 /* nothing */
629}
630
631void repair_worktrees(worktree_repair_fn fn, void *cb_data)
632{
633 struct worktree **worktrees = get_worktrees();
634 struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
635
636 if (!fn)
637 fn = repair_noop;
638 for (; *wt; wt++)
639 repair_gitfile(*wt, fn, cb_data);
640 free_worktrees(worktrees);
641}
Eric Sunshineb214ab52020-08-31 02:57:58 -0400642
643static int is_main_worktree_path(const char *path)
644{
645 struct strbuf target = STRBUF_INIT;
646 struct strbuf maindir = STRBUF_INIT;
647 int cmp;
648
649 strbuf_add_real_path(&target, path);
650 strbuf_strip_suffix(&target, "/.git");
651 strbuf_add_real_path(&maindir, get_git_common_dir());
652 strbuf_strip_suffix(&maindir, "/.git");
653 cmp = fspathcmp(maindir.buf, target.buf);
654
655 strbuf_release(&maindir);
656 strbuf_release(&target);
657 return !cmp;
658}
659
660/*
Eric Sunshinecf76bae2020-12-21 03:16:01 -0500661 * If both the main worktree and linked worktree have been moved, then the
662 * gitfile /path/to/worktree/.git won't point into the repository, thus we
663 * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may
664 * be able to infer the gitdir by manually reading /path/to/worktree/.git,
665 * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
666 */
667static char *infer_backlink(const char *gitfile)
668{
669 struct strbuf actual = STRBUF_INIT;
670 struct strbuf inferred = STRBUF_INIT;
671 const char *id;
672
673 if (strbuf_read_file(&actual, gitfile, 0) < 0)
674 goto error;
675 if (!starts_with(actual.buf, "gitdir:"))
676 goto error;
677 if (!(id = find_last_dir_sep(actual.buf)))
678 goto error;
679 strbuf_trim(&actual);
680 id++; /* advance past '/' to point at <id> */
681 if (!*id)
682 goto error;
683 strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
684 if (!is_directory(inferred.buf))
685 goto error;
686
687 strbuf_release(&actual);
688 return strbuf_detach(&inferred, NULL);
689
690error:
691 strbuf_release(&actual);
692 strbuf_release(&inferred);
693 return NULL;
694}
695
696/*
Eric Sunshineb214ab52020-08-31 02:57:58 -0400697 * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
698 * the worktree's path.
699 */
700void repair_worktree_at_path(const char *path,
701 worktree_repair_fn fn, void *cb_data)
702{
703 struct strbuf dotgit = STRBUF_INIT;
704 struct strbuf realdotgit = STRBUF_INIT;
705 struct strbuf gitdir = STRBUF_INIT;
706 struct strbuf olddotgit = STRBUF_INIT;
707 char *backlink = NULL;
708 const char *repair = NULL;
709 int err;
710
711 if (!fn)
712 fn = repair_noop;
713
714 if (is_main_worktree_path(path))
715 goto done;
716
717 strbuf_addf(&dotgit, "%s/.git", path);
718 if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
719 fn(1, path, _("not a valid path"), cb_data);
720 goto done;
721 }
722
723 backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
724 if (err == READ_GITFILE_ERR_NOT_A_FILE) {
725 fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
726 goto done;
Eric Sunshinecf76bae2020-12-21 03:16:01 -0500727 } else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
728 if (!(backlink = infer_backlink(realdotgit.buf))) {
729 fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
730 goto done;
731 }
Eric Sunshineb214ab52020-08-31 02:57:58 -0400732 } else if (err) {
733 fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
734 goto done;
735 }
736
737 strbuf_addf(&gitdir, "%s/gitdir", backlink);
738 if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
739 repair = _("gitdir unreadable");
740 else {
741 strbuf_rtrim(&olddotgit);
742 if (fspathcmp(olddotgit.buf, realdotgit.buf))
743 repair = _("gitdir incorrect");
744 }
745
746 if (repair) {
747 fn(0, gitdir.buf, repair, cb_data);
748 write_file(gitdir.buf, "%s", realdotgit.buf);
749 }
750done:
751 free(backlink);
752 strbuf_release(&olddotgit);
753 strbuf_release(&gitdir);
754 strbuf_release(&realdotgit);
755 strbuf_release(&dotgit);
756}
Rafael Silvaa29a8b72021-01-19 22:27:33 +0100757
758int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
759{
760 struct stat st;
761 char *path;
762 int fd;
763 size_t len;
764 ssize_t read_result;
765
766 *wtpath = NULL;
767 if (!is_directory(git_path("worktrees/%s", id))) {
768 strbuf_addstr(reason, _("not a valid directory"));
769 return 1;
770 }
771 if (file_exists(git_path("worktrees/%s/locked", id)))
772 return 0;
773 if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
774 strbuf_addstr(reason, _("gitdir file does not exist"));
775 return 1;
776 }
777 fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
778 if (fd < 0) {
779 strbuf_addf(reason, _("unable to read gitdir file (%s)"),
780 strerror(errno));
781 return 1;
782 }
783 len = xsize_t(st.st_size);
784 path = xmallocz(len);
785
786 read_result = read_in_full(fd, path, len);
787 if (read_result < 0) {
788 strbuf_addf(reason, _("unable to read gitdir file (%s)"),
789 strerror(errno));
790 close(fd);
791 free(path);
792 return 1;
793 }
794 close(fd);
795
796 if (read_result != len) {
797 strbuf_addf(reason,
798 _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
799 (uintmax_t)len, (uintmax_t)read_result);
800 free(path);
801 return 1;
802 }
803 while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
804 len--;
805 if (!len) {
806 strbuf_addstr(reason, _("invalid gitdir file"));
807 free(path);
808 return 1;
809 }
810 path[len] = '\0';
811 if (!file_exists(path)) {
812 if (stat(git_path("worktrees/%s/index", id), &st) ||
813 st.st_mtime <= expire) {
814 strbuf_addstr(reason, _("gitdir file points to non-existent location"));
815 free(path);
816 return 1;
817 } else {
818 *wtpath = path;
819 return 0;
820 }
821 }
822 *wtpath = path;
823 return 0;
824}
Derrick Stolee615a84a2022-02-07 21:32:59 +0000825
826static int move_config_setting(const char *key, const char *value,
827 const char *from_file, const char *to_file)
828{
829 if (git_config_set_in_file_gently(to_file, key, value))
830 return error(_("unable to set %s in '%s'"), key, to_file);
831 if (git_config_set_in_file_gently(from_file, key, NULL))
832 return error(_("unable to unset %s in '%s'"), key, from_file);
833 return 0;
834}
835
836int init_worktree_config(struct repository *r)
837{
838 int res = 0;
839 int bare = 0;
840 struct config_set cs = { { 0 } };
841 const char *core_worktree;
842 char *common_config_file;
843 char *main_worktree_file;
844
845 /*
846 * If the extension is already enabled, then we can skip the
847 * upgrade process.
848 */
849 if (repository_format_worktree_config)
850 return 0;
851 if ((res = git_config_set_gently("extensions.worktreeConfig", "true")))
852 return error(_("failed to set extensions.worktreeConfig setting"));
853
854 common_config_file = xstrfmt("%s/config", r->commondir);
855 main_worktree_file = xstrfmt("%s/config.worktree", r->commondir);
856
857 git_configset_init(&cs);
858 git_configset_add_file(&cs, common_config_file);
859
860 /*
861 * If core.bare is true in the common config file, then we need to
862 * move it to the main worktree's config file or it will break all
863 * worktrees. If it is false, then leave it in place because it
864 * _could_ be negating a global core.bare=true.
865 */
866 if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
867 if ((res = move_config_setting("core.bare", "true",
868 common_config_file,
869 main_worktree_file)))
870 goto cleanup;
871 }
872 /*
873 * If core.worktree is set, then the main worktree is located
874 * somewhere different than the parent of the common Git dir.
875 * Relocate that value to avoid breaking all worktrees with this
876 * upgrade to worktree config.
877 */
878 if (!git_configset_get_value(&cs, "core.worktree", &core_worktree)) {
879 if ((res = move_config_setting("core.worktree", core_worktree,
880 common_config_file,
881 main_worktree_file)))
882 goto cleanup;
883 }
884
885 /*
886 * Ensure that we use worktree config for the remaining lifetime
887 * of the current process.
888 */
889 repository_format_worktree_config = 1;
890
891cleanup:
892 git_configset_clear(&cs);
893 free(common_config_file);
894 free(main_worktree_file);
895 return res;
896}