Show changes to 11 files +235 −79
Documentation/config/worktree.txt, Documentation/git-worktree.txt, builtin/worktree.c, t/t0001-init.sh, t/t2400-worktree-add.sh, t/t2401-worktree-prune.sh, t/t2402-worktree-list.sh, t/t2403-worktree-move.sh, t/t2406-worktree-repair.sh, worktree.c, worktree.h
diff --git a/Documentation/config/worktree.txt b/Documentation/config/worktree.txt
index 048e349482df6c892055720eb53cdcd6c327b6ed..d0589c21d034f07f70ca172947520d7d78720cca 100644
--- a/Documentation/config/worktree.txt
+++ b/Documentation/config/worktree.txt
@@ -7,3 +7,8 @@ worktree.guessRemote::
such a branch exists, it is checked out and set as "upstream"
for the new branch. If no such match can be found, it falls
back to creating a new branch from the current HEAD.
+worktree.useRelativePaths::
+ If set to `true`, the worktree paths will be stored relative to the
+ repository’s top-level directory, rather than using absolute paths.
+ This is particularly useful for setups where the repository may be moved
+ between different locations or environments.
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 70437c815f13852bd2eb862176b8b933e6de0acf..4a6ad481ee1d44b43683bd67881a2771e2b090c7 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -216,6 +216,15 @@ To remove a locked worktree, specify `--force` twice.
This can also be set up as the default behaviour by using the
`worktree.guessRemote` config option.
+--[no-]relative-paths::
+ The worktree paths will be stored relative to the repository’s
+ top-level directory, rather than using absolute paths.
+ This is particularly useful for setups where the repository may be moved
+ between different locations or environments.
++
+This can also be set up as the default behaviour by using the
+`worktree.useRelativePaths` config option.
+
--[no-]track::
When creating a new branch, if `<commit-ish>` is a branch,
mark it as "upstream" from the new branch. This is the
diff --git a/builtin/worktree.c b/builtin/worktree.c
index dae63dedf4cac2621f51f95a39aa456b33acd894..bb06830d6fe82aa97833c6e87f034115dfaa23bd 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -134,6 +134,9 @@ static int git_worktree_config(const char *var, const char *value,
if (!strcmp(var, "worktree.guessremote")) {
guess_remote = git_config_bool(var, value);
return 0;
+ } else if (!strcmp(var, "worktree.userelativepaths")) {
+ use_relative_paths = git_config_bool(var, value);
+ return 0;
}
return git_default_config(var, value, ctx, cb);
@@ -414,8 +417,7 @@ static int add_worktree(const char *path, const char *refname,
const struct add_opts *opts)
{
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
- struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT;
- struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT;
+ struct strbuf sb = STRBUF_INIT;
const char *name;
struct strvec child_env = STRVEC_INIT;
unsigned int counter = 0;
@@ -491,10 +493,7 @@ static int add_worktree(const char *path, const char *refname,
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
- strbuf_realpath(&sb_path_realpath, path, 1);
- strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1);
- write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp));
- write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp));
+ write_worktree_linking_files(sb_git, sb);
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
@@ -578,12 +577,9 @@ static int add_worktree(const char *path, const char *refname,
strvec_clear(&child_env);
strbuf_release(&sb);
- strbuf_release(&sb_tmp);
strbuf_release(&symref);
strbuf_release(&sb_repo);
- strbuf_release(&sb_repo_realpath);
strbuf_release(&sb_git);
- strbuf_release(&sb_path_realpath);
strbuf_release(&sb_name);
free_worktree(wt);
return ret;
@@ -796,6 +792,8 @@ static int add(int ac, const char **av, const char *prefix)
PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
OPT_BOOL(0, "guess-remote", &guess_remote,
N_("try to match the new branch name with a remote-tracking branch")),
+ OPT_BOOL(0, "relative-paths", &use_relative_paths,
+ N_("use relative paths for worktrees")),
OPT_END()
};
int ret;
@@ -1189,6 +1187,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)
OPT__FORCE(&force,
N_("force move even if worktree is dirty or locked"),
PARSE_OPT_NOCOMPLETE),
+ OPT_BOOL(0, "relative-paths", &use_relative_paths,
+ N_("use relative paths for worktrees")),
OPT_END()
};
struct worktree **worktrees, *wt;
@@ -1382,6 +1382,8 @@ static int repair(int ac, const char **av, const char *prefix)
const char **p;
const char *self[] = { ".", NULL };
struct option options[] = {
+ OPT_BOOL(0, "relative-paths", &use_relative_paths,
+ N_("use relative paths for worktrees")),
OPT_END()
};
int rc = 0;
diff --git a/t/t0001-init.sh b/t/t0001-init.sh
index 0178aa62a41f1606f2382a4a10ab593ccf11e0e8..e21b9aa5e7f4599af8b20165b50896c9a49ba7ea 100755
--- a/t/t0001-init.sh
+++ b/t/t0001-init.sh
@@ -435,6 +435,7 @@ sep_git_dir_worktree () {
test_when_finished "rm -rf mainwt linkwt seprepo" &&
git init mainwt &&
test_commit -C mainwt gumby &&
+ git -C mainwt config worktree.useRelativePaths "$([ "$2" = "relative" ] && echo true || echo false)" &&
git -C mainwt worktree add --detach ../linkwt &&
git -C "$1" init --separate-git-dir ../seprepo &&
git -C mainwt rev-parse --git-common-dir >expect &&
@@ -442,12 +443,20 @@ sep_git_dir_worktree () {
test_cmp expect actual
}
-test_expect_success 're-init to move gitdir with linked worktrees' '
- sep_git_dir_worktree mainwt
+test_expect_success 're-init to move gitdir with linked worktrees (absolute)' '
+ sep_git_dir_worktree mainwt absolute
'
-test_expect_success 're-init to move gitdir within linked worktree' '
- sep_git_dir_worktree linkwt
+test_expect_success 're-init to move gitdir within linked worktree (absolute)' '
+ sep_git_dir_worktree linkwt absolute
+'
+
+test_expect_success 're-init to move gitdir with linked worktrees (relative)' '
+ sep_git_dir_worktree mainwt relative
+'
+
+test_expect_success 're-init to move gitdir within linked worktree (relative)' '
+ sep_git_dir_worktree linkwt relative
'
test_expect_success MINGW '.git hidden' '
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index cfc4aeb1798c6d023909cec771e5b74e983af5ea..630c13230b3cc762ce8d943e22be8891aa2b1871 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -1207,4 +1207,45 @@ test_expect_success '"add" with initialized submodule, with submodule.recurse se
git -C project-clone -c submodule.recurse worktree add ../project-5
'
+test_expect_success 'can create worktrees with relative paths' '
+ test_when_finished "git worktree remove relative" &&
+ git config worktree.useRelativePaths false &&
+ git worktree add --relative-paths ./relative &&
+ cat relative/.git >actual &&
+ echo "gitdir: ../.git/worktrees/relative" >expect &&
+ test_cmp expect actual &&
+ cat .git/worktrees/relative/gitdir >actual &&
+ echo "../../../relative/.git" >expect &&
+ test_cmp expect actual
+
+'
+
+test_expect_success 'can create worktrees with absolute paths' '
+ git config worktree.useRelativePaths true &&
+ git worktree add ./relative &&
+ cat relative/.git >actual &&
+ echo "gitdir: ../.git/worktrees/relative" >expect &&
+ test_cmp expect actual &&
+ git worktree add --no-relative-paths ./absolute &&
+ cat absolute/.git >actual &&
+ echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'move repo without breaking relative internal links' '
+ test_when_finished rm -rf repo moved &&
+ git init repo &&
+ (
+ cd repo &&
+ git config worktree.useRelativePaths true &&
+ test_commit initial &&
+ git worktree add wt1 &&
+ cd .. &&
+ mv repo moved &&
+ cd moved/wt1 &&
+ git status >out 2>err &&
+ test_must_be_empty err
+ )
+'
+
test_done
diff --git a/t/t2401-worktree-prune.sh b/t/t2401-worktree-prune.sh
index 976d048e3efc74be9cd909ce76d552b3944d2e10..5eb52b9abbf29514dc082c260ebb7a5e8e63aae0 100755
--- a/t/t2401-worktree-prune.sh
+++ b/t/t2401-worktree-prune.sh
@@ -120,11 +120,12 @@ test_expect_success 'prune duplicate (main/linked)' '
! test -d .git/worktrees/wt
'
-test_expect_success 'not prune proper worktrees when run inside linked worktree' '
+test_expect_success 'not prune proper worktrees inside linked worktree with relative paths' '
test_when_finished rm -rf repo wt_ext &&
git init repo &&
(
cd repo &&
+ git config worktree.useRelativePaths true &&
echo content >file &&
git add file &&
git commit -m msg &&
diff --git a/t/t2402-worktree-list.sh b/t/t2402-worktree-list.sh
index 33ea9cb21ba07c9563530b54da06753eaa993fe2..780daa6cd6351f8fa9434619cc212aade8f01420 100755
--- a/t/t2402-worktree-list.sh
+++ b/t/t2402-worktree-list.sh
@@ -261,6 +261,7 @@ test_expect_success 'broken main worktree still at the top' '
'
test_expect_success 'linked worktrees are sorted' '
+ test_when_finished "rm -rf sorted" &&
mkdir sorted &&
git init sorted/main &&
(
@@ -280,6 +281,27 @@ test_expect_success 'linked worktrees are sorted' '
test_cmp expected sorted/main/actual
'
+test_expect_success 'linked worktrees with relative paths are shown with absolute paths' '
+ test_when_finished "rm -rf sorted" &&
+ mkdir sorted &&
+ git init sorted/main &&
+ (
+ cd sorted/main &&
+ test_tick &&
+ test_commit new &&
+ git worktree add --relative-paths ../first &&
+ git worktree add ../second &&
+ git worktree list --porcelain >out &&
+ grep ^worktree out >actual
+ ) &&
+ cat >expected <<-EOF &&
+ worktree $(pwd)/sorted/main
+ worktree $(pwd)/sorted/first
+ worktree $(pwd)/sorted/second
+ EOF
+ test_cmp expected sorted/main/actual
+'
+
test_expect_success 'worktree path when called in .git directory' '
git worktree list >list1 &&
git -C .git worktree list >list2 &&
diff --git a/t/t2403-worktree-move.sh b/t/t2403-worktree-move.sh
index 901342ea09b51a8e832f1109fbb737df84283aa2..6ce9ed3f1e6b3f73d2a290e770233eec30221fe5 100755
--- a/t/t2403-worktree-move.sh
+++ b/t/t2403-worktree-move.sh
@@ -247,4 +247,26 @@ test_expect_success 'not remove a repo with initialized submodule' '
)
'
+test_expect_success 'move worktree with absolute path to relative path' '
+ git config worktree.useRelativePaths false &&
+ git worktree add ./absolute &&
+ git worktree move --relative-paths absolute relative &&
+ cat relative/.git >actual &&
+ echo "gitdir: ../.git/worktrees/absolute" >expect &&
+ test_cmp expect actual &&
+ git config worktree.useRelativePaths true &&
+ git worktree move relative relative2 &&
+ cat relative2/.git >actual &&
+ echo "gitdir: ../.git/worktrees/absolute" >expect &&
+ test_cmp expect actual
+'
+
+test_expect_success 'move worktree with relative path to absolute path' '
+ git config worktree.useRelativePaths true &&
+ git worktree move --no-relative-paths relative2 absolute &&
+ cat absolute/.git >actual &&
+ echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
+ test_cmp expect actual
+'
+
test_done
diff --git a/t/t2406-worktree-repair.sh b/t/t2406-worktree-repair.sh
index 7686e60f6ad186519b275f11a5e14064c905b207..84451e903b2ef3c645c0311faf055c846588baf6 100755
--- a/t/t2406-worktree-repair.sh
+++ b/t/t2406-worktree-repair.sh
@@ -216,4 +216,30 @@ test_expect_success 'repair copied main and linked worktrees' '
test_cmp dup/linked.expect dup/linked/.git
'
+test_expect_success 'repair absolute worktree to use relative paths' '
+ test_when_finished "rm -rf main side sidemoved" &&
+ test_create_repo main &&
+ test_commit -C main init &&
+ git -C main worktree add --detach ../side &&
+ echo "../../../../sidemoved/.git" >expect-gitdir &&
+ echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile &&
+ mv side sidemoved &&
+ git -C main worktree repair --relative-paths ../sidemoved &&
+ test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
+ test_cmp expect-gitfile sidemoved/.git
+'
+
+test_expect_success 'repair relative worktree to use absolute paths' '
+ test_when_finished "rm -rf main side sidemoved" &&
+ test_create_repo main &&
+ test_commit -C main init &&
+ git -C main worktree add --relative-paths --detach ../side &&
+ echo "$(pwd)/sidemoved/.git" >expect-gitdir &&
+ echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile &&
+ mv side sidemoved &&
+ git -C main worktree repair ../sidemoved &&
+ test_cmp expect-gitdir main/.git/worktrees/side/gitdir &&
+ test_cmp expect-gitfile sidemoved/.git
+'
+
test_done
diff --git a/worktree.c b/worktree.c
index 77ff484d3ec48c547ee4e3d958dfa28a52c1eaa7..f4cee73d7a1edecafdff30b6d5e2d9dd1365b93e 100644
--- a/worktree.c
+++ b/worktree.c
@@ -14,6 +14,8 @@
#include "wt-status.h"
#include "config.h"
+int use_relative_paths = 0;
+
void free_worktree(struct worktree *worktree)
{
if (!worktree)
@@ -111,9 +113,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);
@@ -379,29 +381,24 @@ int validate_worktree(const struct worktree *wt, struct strbuf *errmsg,
void update_worktree_location(struct worktree *wt, const char *path_)
{
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);
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,
@@ -580,9 +577,9 @@ static void repair_gitfile(struct worktree *wt,
worktree_repair_fn fn, void *cb_data)
{
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);
}
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,
@@ -651,45 +651,30 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data)
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;
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);
+ if (!is_absolute_path(dotgit.buf)) {
+ 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);
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,12 +710,15 @@ 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)
{
struct strbuf actual = STRBUF_INIT;
const char *id;
+ strbuf_reset(inferred);
if (strbuf_read_file(&actual, gitfile, 0) < 0)
goto error;
if (!starts_with(actual.buf, "gitdir:"))
@@ -741,18 +729,16 @@ static int infer_backlink(const char *gitfile, struct strbuf *inferred)
id++; /* advance past '/' to point at <id> */
if (!*id)
goto error;
- strbuf_reset(inferred);
strbuf_git_common_path(inferred, the_repository, "worktrees/%s", id);
if (!is_directory(inferred->buf))
goto error;
strbuf_release(&actual);
- return 1;
-
+ return inferred->len;
error:
strbuf_release(&actual);
strbuf_reset(inferred); /* clear invalid path */
- return 0;
+ return -1;
}
/*
@@ -763,13 +749,10 @@ void repair_worktree_at_path(const char *path,
worktree_repair_fn fn, void *cb_data)
{
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 +764,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 +795,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 +821,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);
}
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 +1010,29 @@ int init_worktree_config(struct repository *r)
free(main_worktree_file);
return res;
}
+
+void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir)
+{
+ 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) {
+ 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);
+}
diff --git a/worktree.h b/worktree.h
index e96118621638667d87c5d7e0452ed10bd1ddf606..5929089891c97318a8f5329f7938264c717050d5 100644
--- a/worktree.h
+++ b/worktree.h
@@ -5,6 +5,8 @@
struct strbuf;
+extern int use_relative_paths;
+
struct worktree {
/* The repository this worktree belongs to. */
struct repository *repo;
@@ -215,4 +217,16 @@ void strbuf_worktree_ref(const struct worktree *wt,
*/
int init_worktree_config(struct repository *r);
+/**
+ * Write the .git file and gitdir file that links the worktree to the repository.
+ *
+ * The `dotgit` parameter is the path to the worktree's .git file, and `gitdir`
+ * is the path to the repository's `gitdir` file.
+ *
+ * Example
+ * dotgit: "/path/to/foo/.git"
+ * gitdir: "/path/to/repo/worktrees/foo/gitdir"
+ */
+void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir);
+
#endif