🌐 AI搜索 & 代理 主页
blob: 1399d452accaa3d0bdadd759c95fa7fd704f0a95 [file] [log] [blame]
Elijah Newren61a7b982023-03-21 06:26:061#include "git-compat-util.h"
Elijah Newren0b027f62023-03-21 06:25:582#include "abspath.h"
Elijah Newren32a8f512023-03-21 06:26:033#include "environment.h"
Elijah Newrenf394e092023-03-21 06:25:544#include "gettext.h"
Elijah Newrenc3399322023-05-16 06:33:595#include "path.h"
Brandon Williamsb3371722017-06-22 18:43:376#include "repository.h"
Michael Rappazzoac6c5612015-10-02 11:55:317#include "refs.h"
Elijah Newrene38da482023-03-21 06:26:058#include "setup.h"
Michael Rappazzoac6c5612015-10-02 11:55:319#include "strbuf.h"
10#include "worktree.h"
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:2811#include "dir.h"
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 13:01:3312#include "wt-status.h"
Derrick Stolee615a84a2022-02-07 21:32:5913#include "config.h"
Michael Rappazzoac6c5612015-10-02 11:55:3114
Michael Rappazzo51934902015-10-08 17:01:0315void free_worktrees(struct worktree **worktrees)
16{
17 int i = 0;
18
19 for (i = 0; worktrees[i]; i++) {
20 free(worktrees[i]->path);
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 13:01:2621 free(worktrees[i]->id);
Michael Rappazzo92718b72015-10-08 17:01:0422 free(worktrees[i]->head_ref);
Nguyễn Thái Ngọc Duy346ef532016-06-13 12:18:2323 free(worktrees[i]->lock_reason);
Rafael Silvafc0c7d52021-01-19 21:27:3424 free(worktrees[i]->prune_reason);
Michael Rappazzo51934902015-10-08 17:01:0325 free(worktrees[i]);
26 }
27 free (worktrees);
28}
29
Michael Rappazzo51934902015-10-08 17:01:0330/**
Martin Ågrencfaf9f02020-09-27 13:15:4531 * Update head_oid, head_ref and is_detached of the given worktree
Michael Rappazzo92718b72015-10-08 17:01:0432 */
Nguyễn Thái Ngọc Duyfa099d22017-04-24 10:01:2333static void add_head_info(struct worktree *wt)
Michael Rappazzo92718b72015-10-08 17:01:0434{
Nguyễn Thái Ngọc Duyfa099d22017-04-24 10:01:2335 int flags;
36 const char *target;
37
Ævar Arnfjörð Bjarmasonf1da24c2021-10-16 09:39:2738 target = refs_resolve_ref_unsafe(get_worktree_ref_store(wt),
Nguyễn Thái Ngọc Duyfa099d22017-04-24 10:01:2339 "HEAD",
Nguyễn Thái Ngọc Duy31824d12017-08-24 10:41:2440 0,
Ævar Arnfjörð Bjarmasonce14de02022-01-26 14:37:0141 &wt->head_oid, &flags);
Nguyễn Thái Ngọc Duyfa099d22017-04-24 10:01:2342 if (!target)
43 return;
44
45 if (flags & REF_ISSYMREF)
46 wt->head_ref = xstrdup(target);
47 else
48 wt->is_detached = 1;
Michael Rappazzo92718b72015-10-08 17:01:0449}
50
51/**
Michael Rappazzo51934902015-10-08 17:01:0352 * get the main worktree
53 */
54static struct worktree *get_main_worktree(void)
Michael Rappazzo1ceb7f92015-10-08 17:01:0255{
Michael Rappazzo51934902015-10-08 17:01:0356 struct worktree *worktree = NULL;
Michael Rappazzo51934902015-10-08 17:01:0357 struct strbuf worktree_path = STRBUF_INIT;
Michael Rappazzo1ceb7f92015-10-08 17:01:0258
Eric Sunshine918d8ff2020-07-31 23:32:1459 strbuf_add_real_path(&worktree_path, get_git_common_dir());
60 strbuf_strip_suffix(&worktree_path, "/.git");
Michael Rappazzo51934902015-10-08 17:01:0361
René Scharfeca56dad2021-03-13 16:17:2262 CALLOC_ARRAY(worktree, 1);
Michael Rappazzo92718b72015-10-08 17:01:0463 worktree->path = strbuf_detach(&worktree_path, NULL);
Jonathan Tanf3534c92019-04-19 17:21:2864 /*
65 * NEEDSWORK: If this function is called from a secondary worktree and
66 * config.worktree is present, is_bare_repository_cfg will reflect the
67 * contents of config.worktree, not the contents of the main worktree.
68 * This means that worktree->is_bare may be set to 0 even if the main
69 * worktree is configured to be bare.
70 */
71 worktree->is_bare = (is_bare_repository_cfg == 1) ||
72 is_bare_repository();
Nguyễn Thái Ngọc Duyfa099d22017-04-24 10:01:2373 add_head_info(worktree);
Michael Rappazzo51934902015-10-08 17:01:0374 return worktree;
Michael Rappazzo1ceb7f92015-10-08 17:01:0275}
76
Michael Rappazzo51934902015-10-08 17:01:0377static struct worktree *get_linked_worktree(const char *id)
Michael Rappazzoac6c5612015-10-02 11:55:3178{
Michael Rappazzo51934902015-10-08 17:01:0379 struct worktree *worktree = NULL;
Michael Rappazzoac6c5612015-10-02 11:55:3180 struct strbuf path = STRBUF_INIT;
Michael Rappazzo51934902015-10-08 17:01:0381 struct strbuf worktree_path = STRBUF_INIT;
Michael Rappazzoac6c5612015-10-02 11:55:3182
Michael Rappazzo1ceb7f92015-10-08 17:01:0283 if (!id)
84 die("Missing linked worktree name");
Michael Rappazzoac6c5612015-10-02 11:55:3185
Brandon Williamsb3371722017-06-22 18:43:3786 strbuf_git_common_path(&path, the_repository, "worktrees/%s/gitdir", id);
Michael Rappazzo51934902015-10-08 17:01:0387 if (strbuf_read_file(&worktree_path, path.buf, 0) <= 0)
88 /* invalid gitdir file */
89 goto done;
Michael Rappazzo51934902015-10-08 17:01:0390 strbuf_rtrim(&worktree_path);
Eric Sunshine1c4854e2020-07-31 23:32:1391 strbuf_strip_suffix(&worktree_path, "/.git");
Michael Rappazzo51934902015-10-08 17:01:0392
René Scharfeca56dad2021-03-13 16:17:2293 CALLOC_ARRAY(worktree, 1);
Michael Rappazzo92718b72015-10-08 17:01:0494 worktree->path = strbuf_detach(&worktree_path, NULL);
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 13:01:2695 worktree->id = xstrdup(id);
Nguyễn Thái Ngọc Duyfa099d22017-04-24 10:01:2396 add_head_info(worktree);
Michael Rappazzo51934902015-10-08 17:01:0397
Michael Rappazzoac6c5612015-10-02 11:55:3198done:
99 strbuf_release(&path);
Michael Rappazzo51934902015-10-08 17:01:03100 strbuf_release(&worktree_path);
Michael Rappazzo51934902015-10-08 17:01:03101 return worktree;
Michael Rappazzoac6c5612015-10-02 11:55:31102}
103
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28104static void mark_current_worktree(struct worktree **worktrees)
105{
René Scharfe0aaad412017-01-26 17:54:23106 char *git_dir = absolute_pathdup(get_git_dir());
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28107 int i;
108
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28109 for (i = 0; worktrees[i]; i++) {
110 struct worktree *wt = worktrees[i];
Nguyễn Thái Ngọc Duy360af2d2016-05-22 09:33:52111 const char *wt_git_dir = get_worktree_git_dir(wt);
112
113 if (!fspathcmp(git_dir, absolute_path(wt_git_dir))) {
114 wt->is_current = 1;
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28115 break;
Nguyễn Thái Ngọc Duy360af2d2016-05-22 09:33:52116 }
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28117 }
Nguyễn Thái Ngọc Duy360af2d2016-05-22 09:33:52118 free(git_dir);
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28119}
120
Eric Sunshine03f24652020-06-19 23:35:44121struct worktree **get_worktrees(void)
Michael Rappazzoac6c5612015-10-02 11:55:31122{
Michael Rappazzo51934902015-10-08 17:01:03123 struct worktree **list = NULL;
Michael Rappazzoac6c5612015-10-02 11:55:31124 struct strbuf path = STRBUF_INIT;
125 DIR *dir;
126 struct dirent *d;
Michael Rappazzo51934902015-10-08 17:01:03127 int counter = 0, alloc = 2;
Michael Rappazzoac6c5612015-10-02 11:55:31128
René Scharfe3f646992017-02-25 10:30:03129 ALLOC_ARRAY(list, alloc);
Michael Rappazzo51934902015-10-08 17:01:03130
Nguyễn Thái Ngọc Duya2345632016-11-28 09:36:54131 list[counter++] = get_main_worktree();
Michael Rappazzoac6c5612015-10-02 11:55:31132
133 strbuf_addf(&path, "%s/worktrees", get_git_common_dir());
134 dir = opendir(path.buf);
135 strbuf_release(&path);
Michael Rappazzo51934902015-10-08 17:01:03136 if (dir) {
Elijah Newrenb548f0f2021-05-12 17:28:22137 while ((d = readdir_skip_dot_and_dotdot(dir)) != NULL) {
Michael Rappazzo51934902015-10-08 17:01:03138 struct worktree *linked = NULL;
Michael Rappazzoac6c5612015-10-02 11:55:31139
Nguyễn Thái Ngọc Duyd4cddd62016-01-18 11:21:29140 if ((linked = get_linked_worktree(d->d_name))) {
141 ALLOC_GROW(list, counter + 1, alloc);
142 list[counter++] = linked;
143 }
Michael Rappazzo51934902015-10-08 17:01:03144 }
145 closedir(dir);
Michael Rappazzoac6c5612015-10-02 11:55:31146 }
Michael Rappazzo51934902015-10-08 17:01:03147 ALLOC_GROW(list, counter + 1, alloc);
148 list[counter] = NULL;
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28149
Nguyễn Thái Ngọc Duy750e8a62016-04-22 13:01:28150 mark_current_worktree(list);
Michael Rappazzo51934902015-10-08 17:01:03151 return list;
152}
153
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 13:01:26154const char *get_worktree_git_dir(const struct worktree *wt)
Michael Rappazzo51934902015-10-08 17:01:03155{
Nguyễn Thái Ngọc Duy69dfe3b2016-04-22 13:01:26156 if (!wt)
157 return get_git_dir();
158 else if (!wt->id)
159 return get_git_common_dir();
160 else
161 return git_common_path("worktrees/%s", wt->id);
162}
163
Nguyễn Thái Ngọc Duy080739b2016-06-13 12:18:26164static struct worktree *find_worktree_by_suffix(struct worktree **list,
165 const char *suffix)
166{
167 struct worktree *found = NULL;
168 int nr_found = 0, suffixlen;
169
170 suffixlen = strlen(suffix);
171 if (!suffixlen)
172 return NULL;
173
174 for (; *list && nr_found < 2; list++) {
175 const char *path = (*list)->path;
176 int pathlen = strlen(path);
177 int start = pathlen - suffixlen;
178
179 /* suffix must start at directory boundary */
180 if ((!start || (start > 0 && is_dir_sep(path[start - 1]))) &&
181 !fspathcmp(suffix, path + start)) {
182 found = *list;
183 nr_found++;
184 }
185 }
186 return nr_found == 1 ? found : NULL;
187}
188
Nguyễn Thái Ngọc Duy68353142016-06-03 12:19:39189struct worktree *find_worktree(struct worktree **list,
190 const char *prefix,
191 const char *arg)
192{
Nguyễn Thái Ngọc Duy080739b2016-06-13 12:18:26193 struct worktree *wt;
Jeff Kinge4da43b2017-03-21 01:28:49194 char *to_free = NULL;
Nguyễn Thái Ngọc Duy68353142016-06-03 12:19:39195
Nguyễn Thái Ngọc Duy080739b2016-06-13 12:18:26196 if ((wt = find_worktree_by_suffix(list, arg)))
197 return wt;
198
Jeff Kinge4da43b2017-03-21 01:28:49199 if (prefix)
200 arg = to_free = prefix_filename(prefix, arg);
Eric Sunshinebb4995f2020-02-24 09:08:47201 wt = find_worktree_by_path(list, arg);
202 free(to_free);
203 return wt;
204}
205
206struct worktree *find_worktree_by_path(struct worktree **list, const char *p)
207{
Alexandr Miloslavskiy4530a852020-03-10 13:11:23208 struct strbuf wt_path = STRBUF_INIT;
Eric Sunshinebb4995f2020-02-24 09:08:47209 char *path = real_pathdup(p, 0);
210
211 if (!path)
Eric Sunshine4c5fa9e2018-08-28 21:20:18212 return NULL;
Nguyễn Thái Ngọc Duy105df732019-05-13 10:49:44213 for (; *list; list++) {
Alexandr Miloslavskiy4530a852020-03-10 13:11:23214 if (!strbuf_realpath(&wt_path, (*list)->path, 0))
215 continue;
Nguyễn Thái Ngọc Duy105df732019-05-13 10:49:44216
Alexandr Miloslavskiy4530a852020-03-10 13:11:23217 if (!fspathcmp(path, wt_path.buf))
Nguyễn Thái Ngọc Duy68353142016-06-03 12:19:39218 break;
Nguyễn Thái Ngọc Duy105df732019-05-13 10:49:44219 }
Nguyễn Thái Ngọc Duy68353142016-06-03 12:19:39220 free(path);
Alexandr Miloslavskiy4530a852020-03-10 13:11:23221 strbuf_release(&wt_path);
Nguyễn Thái Ngọc Duy68353142016-06-03 12:19:39222 return *list;
223}
224
Nguyễn Thái Ngọc Duy984ad9e2016-06-03 12:19:40225int is_main_worktree(const struct worktree *wt)
226{
227 return !wt->id;
228}
229
Nickolai Belakovskid236f122018-10-30 06:24:09230const char *worktree_lock_reason(struct worktree *wt)
Nguyễn Thái Ngọc Duy346ef532016-06-13 12:18:23231{
Rafael Silvaeb361352021-01-19 21:27:35232 if (is_main_worktree(wt))
233 return NULL;
Nguyễn Thái Ngọc Duy346ef532016-06-13 12:18:23234
235 if (!wt->lock_reason_valid) {
236 struct strbuf path = STRBUF_INIT;
237
238 strbuf_addstr(&path, worktree_git_path(wt, "locked"));
239 if (file_exists(path.buf)) {
240 struct strbuf lock_reason = STRBUF_INIT;
241 if (strbuf_read_file(&lock_reason, path.buf, 0) < 0)
242 die_errno(_("failed to read '%s'"), path.buf);
243 strbuf_trim(&lock_reason);
244 wt->lock_reason = strbuf_detach(&lock_reason, NULL);
245 } else
246 wt->lock_reason = NULL;
247 wt->lock_reason_valid = 1;
248 strbuf_release(&path);
249 }
250
251 return wt->lock_reason;
252}
253
Rafael Silvafc0c7d52021-01-19 21:27:34254const char *worktree_prune_reason(struct worktree *wt, timestamp_t expire)
255{
256 struct strbuf reason = STRBUF_INIT;
257 char *path = NULL;
258
259 if (is_main_worktree(wt))
260 return NULL;
261 if (wt->prune_reason_valid)
262 return wt->prune_reason;
263
264 if (should_prune_worktree(wt->id, &reason, &path, expire))
265 wt->prune_reason = strbuf_detach(&reason, NULL);
266 wt->prune_reason_valid = 1;
267
268 strbuf_release(&reason);
269 free(path);
270 return wt->prune_reason;
271}
272
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 09:53:51273/* convenient wrapper to deal with NULL strbuf */
Ævar Arnfjörð Bjarmason48ca53c2021-07-13 08:05:18274__attribute__((format (printf, 2, 3)))
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 09:53:51275static void strbuf_addf_gently(struct strbuf *buf, const char *fmt, ...)
276{
277 va_list params;
278
279 if (!buf)
280 return;
281
282 va_start(params, fmt);
283 strbuf_vaddf(buf, fmt, params);
284 va_end(params);
285}
286
Nguyễn Thái Ngọc Duyee6763a2018-02-12 09:49:40287int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
288 unsigned flags)
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 09:53:51289{
290 struct strbuf wt_path = STRBUF_INIT;
Alexandr Miloslavskiy3d7747e2020-03-10 13:11:22291 struct strbuf realpath = STRBUF_INIT;
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 09:53:51292 char *path = NULL;
293 int err, ret = -1;
294
295 strbuf_addf(&wt_path, "%s/.git", wt->path);
296
297 if (is_main_worktree(wt)) {
298 if (is_directory(wt_path.buf)) {
299 ret = 0;
300 goto done;
301 }
302 /*
303 * Main worktree using .git file to point to the
304 * repository would make it impossible to know where
305 * the actual worktree is if this function is executed
306 * from another worktree. No .git file support for now.
307 */
308 strbuf_addf_gently(errmsg,
309 _("'%s' at main working tree is not the repository directory"),
310 wt_path.buf);
311 goto done;
312 }
313
314 /*
315 * Make sure "gitdir" file points to a real .git file and that
316 * file points back here.
317 */
318 if (!is_absolute_path(wt->path)) {
319 strbuf_addf_gently(errmsg,
320 _("'%s' file does not contain absolute path to the working tree location"),
321 git_common_path("worktrees/%s/gitdir", wt->id));
322 goto done;
323 }
324
Nguyễn Thái Ngọc Duyee6763a2018-02-12 09:49:40325 if (flags & WT_VALIDATE_WORKTREE_MISSING_OK &&
326 !file_exists(wt->path)) {
327 ret = 0;
328 goto done;
329 }
330
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 09:53:51331 if (!file_exists(wt_path.buf)) {
332 strbuf_addf_gently(errmsg, _("'%s' does not exist"), wt_path.buf);
333 goto done;
334 }
335
336 path = xstrdup_or_null(read_gitfile_gently(wt_path.buf, &err));
337 if (!path) {
338 strbuf_addf_gently(errmsg, _("'%s' is not a .git file, error code %d"),
339 wt_path.buf, err);
340 goto done;
341 }
342
Alexandr Miloslavskiy3d7747e2020-03-10 13:11:22343 strbuf_realpath(&realpath, git_common_path("worktrees/%s", wt->id), 1);
344 ret = fspathcmp(path, realpath.buf);
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 09:53:51345
346 if (ret)
347 strbuf_addf_gently(errmsg, _("'%s' does not point back to '%s'"),
348 wt->path, git_common_path("worktrees/%s", wt->id));
349done:
350 free(path);
351 strbuf_release(&wt_path);
Alexandr Miloslavskiy3d7747e2020-03-10 13:11:22352 strbuf_release(&realpath);
Nguyễn Thái Ngọc Duy4ddddc12018-01-24 09:53:51353 return ret;
354}
355
Nguyễn Thái Ngọc Duy9c620fc2018-02-12 09:49:35356void update_worktree_location(struct worktree *wt, const char *path_)
357{
358 struct strbuf path = STRBUF_INIT;
359
360 if (is_main_worktree(wt))
Johannes Schindelin033abf92018-05-02 09:38:39361 BUG("can't relocate main worktree");
Nguyễn Thái Ngọc Duy9c620fc2018-02-12 09:49:35362
363 strbuf_realpath(&path, path_, 1);
364 if (fspathcmp(wt->path, path.buf)) {
365 write_file(git_common_path("worktrees/%s/gitdir", wt->id),
366 "%s/.git", path.buf);
367 free(wt->path);
368 wt->path = strbuf_detach(&path, NULL);
369 }
370 strbuf_release(&path);
371}
372
Nguyễn Thái Ngọc Duy14ace5b2016-04-22 13:01:36373int is_worktree_being_rebased(const struct worktree *wt,
374 const char *target)
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 13:01:33375{
376 struct wt_status_state state;
377 int found_rebase;
378
379 memset(&state, 0, sizeof(state));
380 found_rebase = wt_status_check_rebase(wt, &state) &&
Martin Ågrena46d1f72020-09-27 13:15:47381 (state.rebase_in_progress ||
382 state.rebase_interactive_in_progress) &&
383 state.branch &&
384 skip_prefix(target, "refs/heads/", &target) &&
385 !strcmp(state.branch, target);
Martin Ågren962dd7e2020-09-27 13:15:43386 wt_status_state_free_buffers(&state);
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 13:01:33387 return found_rebase;
388}
389
Nguyễn Thái Ngọc Duy14ace5b2016-04-22 13:01:36390int is_worktree_being_bisected(const struct worktree *wt,
391 const char *target)
Nguyễn Thái Ngọc Duy04a3dfb2016-04-22 13:01:35392{
393 struct wt_status_state state;
Martin Ågrenfb07bd42020-09-27 13:15:46394 int found_bisect;
Nguyễn Thái Ngọc Duy04a3dfb2016-04-22 13:01:35395
396 memset(&state, 0, sizeof(state));
Martin Ågrenfb07bd42020-09-27 13:15:46397 found_bisect = wt_status_check_bisect(wt, &state) &&
Rubén Justo990adcc2023-09-09 20:12:47398 state.bisecting_from &&
Martin Ågrena46d1f72020-09-27 13:15:47399 skip_prefix(target, "refs/heads/", &target) &&
Rubén Justo990adcc2023-09-09 20:12:47400 !strcmp(state.bisecting_from, target);
Martin Ågren962dd7e2020-09-27 13:15:43401 wt_status_state_free_buffers(&state);
Martin Ågrenfb07bd42020-09-27 13:15:46402 return found_bisect;
Nguyễn Thái Ngọc Duy04a3dfb2016-04-22 13:01:35403}
404
Nguyễn Thái Ngọc Duy8d9fdd72016-04-22 13:01:33405/*
406 * note: this function should be able to detect shared symref even if
407 * HEAD is temporarily detached (e.g. in the middle of rebase or
408 * bisect). New commands that do similar things should update this
409 * function as well.
410 */
Rubén Justo662078c2023-02-25 14:21:51411int is_shared_symref(const struct worktree *wt, const char *symref,
412 const char *target)
413{
414 const char *symref_target;
415 struct ref_store *refs;
416 int flags;
417
418 if (wt->is_bare)
419 return 0;
420
421 if (wt->is_detached && !strcmp(symref, "HEAD")) {
422 if (is_worktree_being_rebased(wt, target))
423 return 1;
424 if (is_worktree_being_bisected(wt, target))
425 return 1;
426 }
427
428 refs = get_worktree_ref_store(wt);
429 symref_target = refs_resolve_ref_unsafe(refs, symref, 0,
430 NULL, &flags);
431 if ((flags & REF_ISSYMREF) &&
432 symref_target && !strcmp(symref_target, target))
433 return 1;
434
435 return 0;
436}
437
Anders Kaseorgc8dd4912021-12-01 22:15:43438const struct worktree *find_shared_symref(struct worktree **worktrees,
439 const char *symref,
Nguyễn Thái Ngọc Duyd3b9ac02016-04-22 13:01:27440 const char *target)
Michael Rappazzo51934902015-10-08 17:01:03441{
Michael Rappazzo51934902015-10-08 17:01:03442
Rubén Justofaa4d592023-02-25 14:22:02443 for (int i = 0; worktrees[i]; i++)
Rubén Justo662078c2023-02-25 14:21:51444 if (is_shared_symref(worktrees[i], symref, target))
445 return worktrees[i];
Nguyễn Thái Ngọc Duyfa099d22017-04-24 10:01:23446
Rubén Justo662078c2023-02-25 14:21:51447 return NULL;
Michael Rappazzoac6c5612015-10-02 11:55:31448}
Stefan Beller1a248cf2016-12-12 19:04:33449
450int submodule_uses_worktrees(const char *path)
451{
452 char *submodule_gitdir;
brian m. carlsone02a7142020-02-22 20:17:41453 struct strbuf sb = STRBUF_INIT, err = STRBUF_INIT;
Stefan Beller1a248cf2016-12-12 19:04:33454 DIR *dir;
455 struct dirent *d;
Stefan Beller7c4be452016-12-27 17:50:13456 int ret = 0;
Martin Ågrene8805af2019-02-28 20:36:28457 struct repository_format format = REPOSITORY_FORMAT_INIT;
Stefan Beller1a248cf2016-12-12 19:04:33458
459 submodule_gitdir = git_pathdup_submodule(path, "%s", "");
460 if (!submodule_gitdir)
461 return 0;
462
463 /* The env would be set for the superproject. */
464 get_common_dir_noenv(&sb, submodule_gitdir);
Johannes Schindelind32de662017-05-04 13:59:19465 free(submodule_gitdir);
Stefan Beller1a248cf2016-12-12 19:04:33466
Stefan Beller1a248cf2016-12-12 19:04:33467 strbuf_addstr(&sb, "/config");
468 read_repository_format(&format, sb.buf);
brian m. carlsone02a7142020-02-22 20:17:41469 if (verify_repository_format(&format, &err)) {
470 strbuf_release(&err);
Stefan Beller1a248cf2016-12-12 19:04:33471 strbuf_release(&sb);
Martin Ågrene8805af2019-02-28 20:36:28472 clear_repository_format(&format);
Stefan Beller1a248cf2016-12-12 19:04:33473 return 1;
474 }
Martin Ågrene8805af2019-02-28 20:36:28475 clear_repository_format(&format);
brian m. carlsone02a7142020-02-22 20:17:41476 strbuf_release(&err);
Stefan Beller1a248cf2016-12-12 19:04:33477
478 /* Replace config by worktrees. */
479 strbuf_setlen(&sb, sb.len - strlen("config"));
480 strbuf_addstr(&sb, "worktrees");
481
482 /* See if there is any file inside the worktrees directory. */
483 dir = opendir(sb.buf);
484 strbuf_release(&sb);
Stefan Beller1a248cf2016-12-12 19:04:33485
486 if (!dir)
487 return 0;
488
Elijah Newrenb548f0f2021-05-12 17:28:22489 d = readdir_skip_dot_and_dotdot(dir);
Junio C Hamanoafe8a902022-05-02 16:50:37490 if (d)
Stefan Beller1a248cf2016-12-12 19:04:33491 ret = 1;
Stefan Beller1a248cf2016-12-12 19:04:33492 closedir(dir);
493 return ret;
494}
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 12:36:59495
Nguyễn Thái Ngọc Duyab3e1f72018-10-21 08:08:56496void strbuf_worktree_ref(const struct worktree *wt,
497 struct strbuf *sb,
498 const char *refname)
499{
Han-Wen Nienhuys71e54732022-09-19 16:34:50500 if (parse_worktree_ref(refname, NULL, NULL, NULL) ==
501 REF_WORKTREE_CURRENT &&
502 wt && !wt->is_current) {
503 if (is_main_worktree(wt))
504 strbuf_addstr(sb, "main-worktree/");
505 else
506 strbuf_addf(sb, "worktrees/%s/", wt->id);
Nguyễn Thái Ngọc Duyab3e1f72018-10-21 08:08:56507 }
508 strbuf_addstr(sb, refname);
509}
510
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 12:36:59511int other_head_refs(each_ref_fn fn, void *cb_data)
512{
513 struct worktree **worktrees, **p;
Martin Ågrenef2d5542020-09-27 13:15:44514 struct strbuf refname = STRBUF_INIT;
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 12:36:59515 int ret = 0;
516
Eric Sunshine03f24652020-06-19 23:35:44517 worktrees = get_worktrees();
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 12:36:59518 for (p = worktrees; *p; p++) {
519 struct worktree *wt = *p;
Nguyễn Thái Ngọc Duyab3e1f72018-10-21 08:08:56520 struct object_id oid;
521 int flag;
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 12:36:59522
523 if (wt->is_current)
524 continue;
525
Martin Ågrenef2d5542020-09-27 13:15:44526 strbuf_reset(&refname);
527 strbuf_worktree_ref(wt, &refname, "HEAD");
Ævar Arnfjörð Bjarmasonf1da24c2021-10-16 09:39:27528 if (refs_resolve_ref_unsafe(get_main_ref_store(the_repository),
Ævar Arnfjörð Bjarmason76887df2021-10-16 09:39:14529 refname.buf,
530 RESOLVE_REF_READING,
Ævar Arnfjörð Bjarmasonce14de02022-01-26 14:37:01531 &oid, &flag))
Martin Ågrenef2d5542020-09-27 13:15:44532 ret = fn(refname.buf, &oid, flag, cb_data);
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 12:36:59533 if (ret)
534 break;
535 }
536 free_worktrees(worktrees);
Martin Ågrenef2d5542020-09-27 13:15:44537 strbuf_release(&refname);
Nguyễn Thái Ngọc Duyd0c39a42017-08-23 12:36:59538 return ret;
539}
Eric Sunshinebdd1f3e2020-08-31 06:57:57540
541/*
542 * Repair worktree's /path/to/worktree/.git file if missing, corrupt, or not
543 * pointing at <repo>/worktrees/<id>.
544 */
545static void repair_gitfile(struct worktree *wt,
546 worktree_repair_fn fn, void *cb_data)
547{
548 struct strbuf dotgit = STRBUF_INIT;
549 struct strbuf repo = STRBUF_INIT;
550 char *backlink;
551 const char *repair = NULL;
552 int err;
553
554 /* missing worktree can't be repaired */
555 if (!file_exists(wt->path))
556 return;
557
558 if (!is_directory(wt->path)) {
559 fn(1, wt->path, _("not a directory"), cb_data);
560 return;
561 }
562
563 strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1);
564 strbuf_addf(&dotgit, "%s/.git", wt->path);
565 backlink = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err));
566
567 if (err == READ_GITFILE_ERR_NOT_A_FILE)
568 fn(1, wt->path, _(".git is not a file"), cb_data);
569 else if (err)
570 repair = _(".git file broken");
571 else if (fspathcmp(backlink, repo.buf))
572 repair = _(".git file incorrect");
573
574 if (repair) {
575 fn(0, wt->path, repair, cb_data);
576 write_file(dotgit.buf, "gitdir: %s", repo.buf);
577 }
578
579 free(backlink);
580 strbuf_release(&repo);
581 strbuf_release(&dotgit);
582}
583
Jeff King2b0e46f2023-08-29 23:45:31584static void repair_noop(int iserr UNUSED,
585 const char *path UNUSED,
586 const char *msg UNUSED,
587 void *cb_data UNUSED)
Eric Sunshinebdd1f3e2020-08-31 06:57:57588{
589 /* nothing */
590}
591
592void repair_worktrees(worktree_repair_fn fn, void *cb_data)
593{
594 struct worktree **worktrees = get_worktrees();
595 struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
596
597 if (!fn)
598 fn = repair_noop;
599 for (; *wt; wt++)
600 repair_gitfile(*wt, fn, cb_data);
601 free_worktrees(worktrees);
602}
Eric Sunshineb214ab52020-08-31 06:57:58603
604static int is_main_worktree_path(const char *path)
605{
606 struct strbuf target = STRBUF_INIT;
607 struct strbuf maindir = STRBUF_INIT;
608 int cmp;
609
610 strbuf_add_real_path(&target, path);
611 strbuf_strip_suffix(&target, "/.git");
612 strbuf_add_real_path(&maindir, get_git_common_dir());
613 strbuf_strip_suffix(&maindir, "/.git");
614 cmp = fspathcmp(maindir.buf, target.buf);
615
616 strbuf_release(&maindir);
617 strbuf_release(&target);
618 return !cmp;
619}
620
621/*
Eric Sunshinecf76bae2020-12-21 08:16:01622 * If both the main worktree and linked worktree have been moved, then the
623 * gitfile /path/to/worktree/.git won't point into the repository, thus we
624 * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may
625 * be able to infer the gitdir by manually reading /path/to/worktree/.git,
626 * extracting the <id>, and checking if <repo>/worktrees/<id> exists.
627 */
628static char *infer_backlink(const char *gitfile)
629{
630 struct strbuf actual = STRBUF_INIT;
631 struct strbuf inferred = STRBUF_INIT;
632 const char *id;
633
634 if (strbuf_read_file(&actual, gitfile, 0) < 0)
635 goto error;
636 if (!starts_with(actual.buf, "gitdir:"))
637 goto error;
638 if (!(id = find_last_dir_sep(actual.buf)))
639 goto error;
640 strbuf_trim(&actual);
641 id++; /* advance past '/' to point at <id> */
642 if (!*id)
643 goto error;
644 strbuf_git_common_path(&inferred, the_repository, "worktrees/%s", id);
645 if (!is_directory(inferred.buf))
646 goto error;
647
648 strbuf_release(&actual);
649 return strbuf_detach(&inferred, NULL);
650
651error:
652 strbuf_release(&actual);
653 strbuf_release(&inferred);
654 return NULL;
655}
656
657/*
Eric Sunshineb214ab52020-08-31 06:57:58658 * Repair <repo>/worktrees/<id>/gitdir if missing, corrupt, or not pointing at
659 * the worktree's path.
660 */
661void repair_worktree_at_path(const char *path,
662 worktree_repair_fn fn, void *cb_data)
663{
664 struct strbuf dotgit = STRBUF_INIT;
665 struct strbuf realdotgit = STRBUF_INIT;
666 struct strbuf gitdir = STRBUF_INIT;
667 struct strbuf olddotgit = STRBUF_INIT;
668 char *backlink = NULL;
669 const char *repair = NULL;
670 int err;
671
672 if (!fn)
673 fn = repair_noop;
674
675 if (is_main_worktree_path(path))
676 goto done;
677
678 strbuf_addf(&dotgit, "%s/.git", path);
679 if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) {
680 fn(1, path, _("not a valid path"), cb_data);
681 goto done;
682 }
683
684 backlink = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err));
685 if (err == READ_GITFILE_ERR_NOT_A_FILE) {
686 fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data);
687 goto done;
Eric Sunshinecf76bae2020-12-21 08:16:01688 } else if (err == READ_GITFILE_ERR_NOT_A_REPO) {
689 if (!(backlink = infer_backlink(realdotgit.buf))) {
690 fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data);
691 goto done;
692 }
Eric Sunshineb214ab52020-08-31 06:57:58693 } else if (err) {
694 fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data);
695 goto done;
696 }
697
698 strbuf_addf(&gitdir, "%s/gitdir", backlink);
699 if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0)
700 repair = _("gitdir unreadable");
701 else {
702 strbuf_rtrim(&olddotgit);
703 if (fspathcmp(olddotgit.buf, realdotgit.buf))
704 repair = _("gitdir incorrect");
705 }
706
707 if (repair) {
708 fn(0, gitdir.buf, repair, cb_data);
709 write_file(gitdir.buf, "%s", realdotgit.buf);
710 }
711done:
712 free(backlink);
713 strbuf_release(&olddotgit);
714 strbuf_release(&gitdir);
715 strbuf_release(&realdotgit);
716 strbuf_release(&dotgit);
717}
Rafael Silvaa29a8b72021-01-19 21:27:33718
719int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire)
720{
721 struct stat st;
722 char *path;
723 int fd;
724 size_t len;
725 ssize_t read_result;
726
727 *wtpath = NULL;
728 if (!is_directory(git_path("worktrees/%s", id))) {
729 strbuf_addstr(reason, _("not a valid directory"));
730 return 1;
731 }
732 if (file_exists(git_path("worktrees/%s/locked", id)))
733 return 0;
734 if (stat(git_path("worktrees/%s/gitdir", id), &st)) {
735 strbuf_addstr(reason, _("gitdir file does not exist"));
736 return 1;
737 }
738 fd = open(git_path("worktrees/%s/gitdir", id), O_RDONLY);
739 if (fd < 0) {
740 strbuf_addf(reason, _("unable to read gitdir file (%s)"),
741 strerror(errno));
742 return 1;
743 }
744 len = xsize_t(st.st_size);
745 path = xmallocz(len);
746
747 read_result = read_in_full(fd, path, len);
748 if (read_result < 0) {
749 strbuf_addf(reason, _("unable to read gitdir file (%s)"),
750 strerror(errno));
751 close(fd);
752 free(path);
753 return 1;
754 }
755 close(fd);
756
757 if (read_result != len) {
758 strbuf_addf(reason,
759 _("short read (expected %"PRIuMAX" bytes, read %"PRIuMAX")"),
760 (uintmax_t)len, (uintmax_t)read_result);
761 free(path);
762 return 1;
763 }
764 while (len && (path[len - 1] == '\n' || path[len - 1] == '\r'))
765 len--;
766 if (!len) {
767 strbuf_addstr(reason, _("invalid gitdir file"));
768 free(path);
769 return 1;
770 }
771 path[len] = '\0';
772 if (!file_exists(path)) {
773 if (stat(git_path("worktrees/%s/index", id), &st) ||
774 st.st_mtime <= expire) {
775 strbuf_addstr(reason, _("gitdir file points to non-existent location"));
776 free(path);
777 return 1;
778 } else {
779 *wtpath = path;
780 return 0;
781 }
782 }
783 *wtpath = path;
784 return 0;
785}
Derrick Stolee615a84a2022-02-07 21:32:59786
787static int move_config_setting(const char *key, const char *value,
788 const char *from_file, const char *to_file)
789{
790 if (git_config_set_in_file_gently(to_file, key, value))
791 return error(_("unable to set %s in '%s'"), key, to_file);
792 if (git_config_set_in_file_gently(from_file, key, NULL))
793 return error(_("unable to unset %s in '%s'"), key, from_file);
794 return 0;
795}
796
797int init_worktree_config(struct repository *r)
798{
799 int res = 0;
800 int bare = 0;
801 struct config_set cs = { { 0 } };
802 const char *core_worktree;
803 char *common_config_file;
804 char *main_worktree_file;
805
806 /*
807 * If the extension is already enabled, then we can skip the
808 * upgrade process.
809 */
Victoria Dye3867f6d2023-05-26 01:33:00810 if (r->repository_format_worktree_config)
Derrick Stolee615a84a2022-02-07 21:32:59811 return 0;
812 if ((res = git_config_set_gently("extensions.worktreeConfig", "true")))
813 return error(_("failed to set extensions.worktreeConfig setting"));
814
815 common_config_file = xstrfmt("%s/config", r->commondir);
816 main_worktree_file = xstrfmt("%s/config.worktree", r->commondir);
817
818 git_configset_init(&cs);
819 git_configset_add_file(&cs, common_config_file);
820
821 /*
822 * If core.bare is true in the common config file, then we need to
823 * move it to the main worktree's config file or it will break all
824 * worktrees. If it is false, then leave it in place because it
825 * _could_ be negating a global core.bare=true.
826 */
827 if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
828 if ((res = move_config_setting("core.bare", "true",
829 common_config_file,
830 main_worktree_file)))
831 goto cleanup;
832 }
833 /*
834 * If core.worktree is set, then the main worktree is located
835 * somewhere different than the parent of the common Git dir.
836 * Relocate that value to avoid breaking all worktrees with this
837 * upgrade to worktree config.
838 */
Glen Choo8868b1e2023-06-28 19:26:27839 if (!git_configset_get_value(&cs, "core.worktree", &core_worktree, NULL)) {
Derrick Stolee615a84a2022-02-07 21:32:59840 if ((res = move_config_setting("core.worktree", core_worktree,
841 common_config_file,
842 main_worktree_file)))
843 goto cleanup;
844 }
845
846 /*
847 * Ensure that we use worktree config for the remaining lifetime
848 * of the current process.
849 */
Victoria Dye3867f6d2023-05-26 01:33:00850 r->repository_format_worktree_config = 1;
Derrick Stolee615a84a2022-02-07 21:32:59851
852cleanup:
853 git_configset_clear(&cs);
854 free(common_config_file);
855 free(main_worktree_file);
856 return res;
857}