diff options
Diffstat (limited to 'worktree.c')
-rw-r--r-- | worktree.c | 157 |
1 files changed, 87 insertions, 70 deletions
diff --git a/worktree.c b/worktree.c index 77ff484d3e..af68b24f9d 100644 --- a/worktree.c +++ b/worktree.c @@ -111,9 +111,9 @@ struct worktree *get_linked_worktree(const char *id, strbuf_strip_suffix(&worktree_path, "/.git"); if (!is_absolute_path(worktree_path.buf)) { - strbuf_strip_suffix(&path, "gitdir"); - strbuf_addbuf(&path, &worktree_path); - strbuf_realpath_forgiving(&worktree_path, path.buf, 0); + strbuf_strip_suffix(&path, "gitdir"); + strbuf_addbuf(&path, &worktree_path); + strbuf_realpath_forgiving(&worktree_path, path.buf, 0); } CALLOC_ARRAY(worktree, 1); @@ -376,32 +376,28 @@ done: return ret; } -void update_worktree_location(struct worktree *wt, const char *path_) +void update_worktree_location(struct worktree *wt, const char *path_, + int use_relative_paths) { struct strbuf path = STRBUF_INIT; - struct strbuf repo = STRBUF_INIT; - struct strbuf file = STRBUF_INIT; - struct strbuf tmp = STRBUF_INIT; + struct strbuf dotgit = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; if (is_main_worktree(wt)) BUG("can't relocate main worktree"); - strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); + strbuf_realpath(&gitdir, git_common_path("worktrees/%s/gitdir", wt->id), 1); strbuf_realpath(&path, path_, 1); + strbuf_addf(&dotgit, "%s/.git", path.buf); if (fspathcmp(wt->path, path.buf)) { - strbuf_addf(&file, "%s/gitdir", repo.buf); - write_file(file.buf, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); - strbuf_reset(&file); - strbuf_addf(&file, "%s/.git", path.buf); - write_file(file.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); + write_worktree_linking_files(dotgit, gitdir, use_relative_paths); free(wt->path); wt->path = strbuf_detach(&path, NULL); } strbuf_release(&path); - strbuf_release(&repo); - strbuf_release(&file); - strbuf_release(&tmp); + strbuf_release(&dotgit); + strbuf_release(&gitdir); } int is_worktree_being_rebased(const struct worktree *wt, @@ -577,12 +573,13 @@ int other_head_refs(each_ref_fn fn, void *cb_data) * pointing at <repo>/worktrees/<id>. */ static void repair_gitfile(struct worktree *wt, - worktree_repair_fn fn, void *cb_data) + worktree_repair_fn fn, void *cb_data, + int use_relative_paths) { struct strbuf dotgit = STRBUF_INIT; + struct strbuf gitdir = STRBUF_INIT; struct strbuf repo = STRBUF_INIT; struct strbuf backlink = STRBUF_INIT; - struct strbuf tmp = STRBUF_INIT; char *dotgit_contents = NULL; const char *repair = NULL; int err; @@ -598,6 +595,7 @@ static void repair_gitfile(struct worktree *wt, strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); strbuf_addf(&dotgit, "%s/.git", wt->path); + strbuf_addf(&gitdir, "%s/gitdir", repo.buf); dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); if (dotgit_contents) { @@ -615,18 +613,20 @@ static void repair_gitfile(struct worktree *wt, repair = _(".git file broken"); else if (fspathcmp(backlink.buf, repo.buf)) repair = _(".git file incorrect"); + else if (use_relative_paths == is_absolute_path(dotgit_contents)) + repair = _(".git file absolute/relative path mismatch"); if (repair) { fn(0, wt->path, repair, cb_data); - write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, wt->path, &tmp)); + write_worktree_linking_files(dotgit, gitdir, use_relative_paths); } done: free(dotgit_contents); strbuf_release(&repo); strbuf_release(&dotgit); + strbuf_release(&gitdir); strbuf_release(&backlink); - strbuf_release(&tmp); } static void repair_noop(int iserr UNUSED, @@ -637,7 +637,7 @@ static void repair_noop(int iserr UNUSED, /* nothing */ } -void repair_worktrees(worktree_repair_fn fn, void *cb_data) +void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths) { struct worktree **worktrees = get_worktrees_internal(1); struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ @@ -645,51 +645,38 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data) if (!fn) fn = repair_noop; for (; *wt; wt++) - repair_gitfile(*wt, fn, cb_data); + repair_gitfile(*wt, fn, cb_data, use_relative_paths); free_worktrees(worktrees); } void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path) { - struct strbuf path = STRBUF_INIT; - struct strbuf repo = STRBUF_INIT; struct strbuf gitdir = STRBUF_INIT; struct strbuf dotgit = STRBUF_INIT; - struct strbuf olddotgit = STRBUF_INIT; - struct strbuf tmp = STRBUF_INIT; + int is_relative_path; if (is_main_worktree(wt)) goto done; - strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); - strbuf_addf(&gitdir, "%s/gitdir", repo.buf); + strbuf_realpath(&gitdir, git_common_path("worktrees/%s/gitdir", wt->id), 1); - if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) + if (strbuf_read_file(&dotgit, gitdir.buf, 0) < 0) goto done; - strbuf_rtrim(&olddotgit); - if (is_absolute_path(olddotgit.buf)) { - strbuf_addbuf(&dotgit, &olddotgit); - } else { - strbuf_addf(&dotgit, "%s/worktrees/%s/%s", old_path, wt->id, olddotgit.buf); + strbuf_rtrim(&dotgit); + is_relative_path = ! is_absolute_path(dotgit.buf); + if (is_relative_path) { + strbuf_insertf(&dotgit, 0, "%s/worktrees/%s/", old_path, wt->id); strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0); } if (!file_exists(dotgit.buf)) goto done; - strbuf_addbuf(&path, &dotgit); - strbuf_strip_suffix(&path, "/.git"); - - write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); - write_file(gitdir.buf, "%s", relative_path(dotgit.buf, repo.buf, &tmp)); + write_worktree_linking_files(dotgit, gitdir, is_relative_path); done: - strbuf_release(&path); - strbuf_release(&repo); strbuf_release(&gitdir); strbuf_release(&dotgit); - strbuf_release(&olddotgit); - strbuf_release(&tmp); } void repair_worktrees_after_gitdir_move(const char *old_path) @@ -725,8 +712,10 @@ static int is_main_worktree_path(const char *path) * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may * be able to infer the gitdir by manually reading /path/to/worktree/.git, * extracting the <id>, and checking if <repo>/worktrees/<id> exists. + * + * Returns -1 on failure and strbuf.len on success. */ -static int infer_backlink(const char *gitfile, struct strbuf *inferred) +static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred) { struct strbuf actual = STRBUF_INIT; const char *id; @@ -747,12 +736,11 @@ static int infer_backlink(const char *gitfile, struct strbuf *inferred) goto error; strbuf_release(&actual); - return 1; - + return inferred->len; error: strbuf_release(&actual); strbuf_reset(inferred); /* clear invalid path */ - return 0; + return -1; } /* @@ -760,16 +748,14 @@ error: * the worktree's path. */ void repair_worktree_at_path(const char *path, - worktree_repair_fn fn, void *cb_data) + worktree_repair_fn fn, void *cb_data, + int use_relative_paths) { struct strbuf dotgit = STRBUF_INIT; - struct strbuf realdotgit = STRBUF_INIT; struct strbuf backlink = STRBUF_INIT; struct strbuf inferred_backlink = STRBUF_INIT; struct strbuf gitdir = STRBUF_INIT; struct strbuf olddotgit = STRBUF_INIT; - struct strbuf realolddotgit = STRBUF_INIT; - struct strbuf tmp = STRBUF_INIT; char *dotgit_contents = NULL; const char *repair = NULL; int err; @@ -781,25 +767,25 @@ void repair_worktree_at_path(const char *path, goto done; strbuf_addf(&dotgit, "%s/.git", path); - if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) { + if (!strbuf_realpath(&dotgit, dotgit.buf, 0)) { fn(1, path, _("not a valid path"), cb_data); goto done; } - infer_backlink(realdotgit.buf, &inferred_backlink); + infer_backlink(dotgit.buf, &inferred_backlink); strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0); - dotgit_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err)); + dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); if (dotgit_contents) { if (is_absolute_path(dotgit_contents)) { strbuf_addstr(&backlink, dotgit_contents); } else { - strbuf_addbuf(&backlink, &realdotgit); + strbuf_addbuf(&backlink, &dotgit); strbuf_strip_suffix(&backlink, ".git"); strbuf_addstr(&backlink, dotgit_contents); strbuf_realpath_forgiving(&backlink, backlink.buf, 0); } } else if (err == READ_GITFILE_ERR_NOT_A_FILE) { - fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data); + fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data); goto done; } else if (err == READ_GITFILE_ERR_NOT_A_REPO) { if (inferred_backlink.len) { @@ -812,11 +798,11 @@ void repair_worktree_at_path(const char *path, */ strbuf_swap(&backlink, &inferred_backlink); } else { - fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data); + fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data); goto done; } } else { - fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data); + fn(1, dotgit.buf, _("unable to locate repository; .git file broken"), cb_data); goto done; } @@ -838,39 +824,35 @@ void repair_worktree_at_path(const char *path, * in the "copy" repository. In this case, point the "copy" worktree's * .git file at the "copy" repository. */ - if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) { + if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) strbuf_swap(&backlink, &inferred_backlink); - } strbuf_addf(&gitdir, "%s/gitdir", backlink.buf); if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) repair = _("gitdir unreadable"); + else if (use_relative_paths == is_absolute_path(olddotgit.buf)) + repair = _("gitdir absolute/relative path mismatch"); else { strbuf_rtrim(&olddotgit); - if (is_absolute_path(olddotgit.buf)) { - strbuf_addbuf(&realolddotgit, &olddotgit); - } else { - strbuf_addf(&realolddotgit, "%s/%s", backlink.buf, olddotgit.buf); - strbuf_realpath_forgiving(&realolddotgit, realolddotgit.buf, 0); + if (!is_absolute_path(olddotgit.buf)) { + strbuf_insertf(&olddotgit, 0, "%s/", backlink.buf); + strbuf_realpath_forgiving(&olddotgit, olddotgit.buf, 0); } - if (fspathcmp(realolddotgit.buf, realdotgit.buf)) + if (fspathcmp(olddotgit.buf, dotgit.buf)) repair = _("gitdir incorrect"); } if (repair) { fn(0, gitdir.buf, repair, cb_data); - write_file(gitdir.buf, "%s", relative_path(realdotgit.buf, backlink.buf, &tmp)); + write_worktree_linking_files(dotgit, gitdir, use_relative_paths); } done: free(dotgit_contents); strbuf_release(&olddotgit); - strbuf_release(&realolddotgit); strbuf_release(&backlink); strbuf_release(&inferred_backlink); strbuf_release(&gitdir); - strbuf_release(&realdotgit); strbuf_release(&dotgit); - strbuf_release(&tmp); } int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire) @@ -1031,3 +1013,38 @@ cleanup: free(main_worktree_file); return res; } + +void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, + int use_relative_paths) +{ + struct strbuf path = STRBUF_INIT; + struct strbuf repo = STRBUF_INIT; + struct strbuf tmp = STRBUF_INIT; + + strbuf_addbuf(&path, &dotgit); + strbuf_strip_suffix(&path, "/.git"); + strbuf_realpath(&path, path.buf, 1); + strbuf_addbuf(&repo, &gitdir); + strbuf_strip_suffix(&repo, "/gitdir"); + strbuf_realpath(&repo, repo.buf, 1); + + if (use_relative_paths && !the_repository->repository_format_relative_worktrees) { + if (upgrade_repository_format(1) < 0) + die(_("unable to upgrade repository format to support relative worktrees")); + if (git_config_set_gently("extensions.relativeWorktrees", "true")) + die(_("unable to set extensions.relativeWorktrees setting")); + the_repository->repository_format_relative_worktrees = 1; + } + + if (use_relative_paths) { + write_file(gitdir.buf, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); + write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); + } else { + write_file(gitdir.buf, "%s/.git", path.buf); + write_file(dotgit.buf, "gitdir: %s", repo.buf); + } + + strbuf_release(&path); + strbuf_release(&repo); + strbuf_release(&tmp); +} |