Skip to content

Commit 717af91

Browse files
calebdwgitster
authored andcommitted
worktree: link worktrees with relative paths
Git currently stores absolute paths to both the main repository and linked worktrees. However, this causes problems when moving repositories or working in containerized environments where absolute paths differ between systems. The worktree links break, and users are required to manually execute `worktree repair` to repair them, leading to workflow disruptions. Additionally, mapping repositories inside of containerized environments renders the repository unusable inside the containers, and this is not repairable as repairing the worktrees inside the containers will result in them being broken outside the containers. To address this, this patch makes Git always write relative paths when linking worktrees. Relative paths increase the resilience of the worktree links across various systems and environments, particularly when the worktrees are self-contained inside the main repository (such as when using a bare repository with worktrees). This improves portability, workflow efficiency, and reduces overall breakages. Although Git now writes relative paths, existing repositories with absolute paths are still supported. There are no breaking changes to workflows based on absolute paths, ensuring backward compatibility. At a low level, the changes involve modifying functions in `worktree.c` and `builtin/worktree.c` to use `relative_path()` when writing the worktree’s `.git` file and the main repository’s `gitdir` reference. Instead of hardcoding absolute paths, Git now computes the relative path between the worktree and the repository, ensuring that these links are portable. Locations where these respective file are read have also been updated to properly handle both absolute and relative paths. Generally, relative paths are always resolved into absolute paths before any operations or comparisons are performed. Additionally, `repair_worktrees_after_gitdir_move()` has been introduced to address the case where both the `<worktree>/.git` and `<repo>/worktrees/<id>/gitdir` links are broken after the gitdir is moved (such as during a re-initialization). This function repairs both sides of the worktree link using the old gitdir path to reestablish the correct paths after a move. The `worktree.path` struct member has also been updated to always store the absolute path of a worktree. This ensures that worktree consumers never have to worry about trying to resolve the absolute path themselves. Signed-off-by: Caleb White <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent bb4a883 commit 717af91

File tree

5 files changed

+223
-51
lines changed

5 files changed

+223
-51
lines changed

builtin/worktree.c

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -414,7 +414,8 @@ static int add_worktree(const char *path, const char *refname,
414414
const struct add_opts *opts)
415415
{
416416
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
417-
struct strbuf sb = STRBUF_INIT, realpath = STRBUF_INIT;
417+
struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT;
418+
struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT;
418419
const char *name;
419420
struct strvec child_env = STRVEC_INIT;
420421
unsigned int counter = 0;
@@ -490,11 +491,10 @@ static int add_worktree(const char *path, const char *refname,
490491

491492
strbuf_reset(&sb);
492493
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
493-
strbuf_realpath(&realpath, sb_git.buf, 1);
494-
write_file(sb.buf, "%s", realpath.buf);
495-
strbuf_realpath(&realpath, repo_get_common_dir(the_repository), 1);
496-
write_file(sb_git.buf, "gitdir: %s/worktrees/%s",
497-
realpath.buf, name);
494+
strbuf_realpath(&sb_path_realpath, path, 1);
495+
strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1);
496+
write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp));
497+
write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp));
498498
strbuf_reset(&sb);
499499
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
500500
write_file(sb.buf, "../..");
@@ -578,11 +578,13 @@ static int add_worktree(const char *path, const char *refname,
578578

579579
strvec_clear(&child_env);
580580
strbuf_release(&sb);
581+
strbuf_release(&sb_tmp);
581582
strbuf_release(&symref);
582583
strbuf_release(&sb_repo);
584+
strbuf_release(&sb_repo_realpath);
583585
strbuf_release(&sb_git);
586+
strbuf_release(&sb_path_realpath);
584587
strbuf_release(&sb_name);
585-
strbuf_release(&realpath);
586588
free_worktree(wt);
587589
return ret;
588590
}

setup.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2420,7 +2420,7 @@ static void separate_git_dir(const char *git_dir, const char *git_link)
24202420

24212421
if (rename(src, git_dir))
24222422
die_errno(_("unable to move %s to %s"), src, git_dir);
2423-
repair_worktrees(NULL, NULL);
2423+
repair_worktrees_after_gitdir_move(src);
24242424
}
24252425

24262426
write_file(git_link, "gitdir: %s", git_dir);

t/t2408-worktree-relative.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
#!/bin/sh
2+
3+
test_description='test worktrees linked with relative paths'
4+
5+
TEST_PASSES_SANITIZE_LEAK=true
6+
. ./test-lib.sh
7+
8+
test_expect_success 'links worktrees with relative paths' '
9+
test_when_finished rm -rf repo &&
10+
git init repo &&
11+
(
12+
cd repo &&
13+
test_commit initial &&
14+
git worktree add wt1 &&
15+
echo "../../../wt1/.git" >expected_gitdir &&
16+
cat .git/worktrees/wt1/gitdir >actual_gitdir &&
17+
echo "gitdir: ../.git/worktrees/wt1" >expected_git &&
18+
cat wt1/.git >actual_git &&
19+
test_cmp expected_gitdir actual_gitdir &&
20+
test_cmp expected_git actual_git
21+
)
22+
'
23+
24+
test_expect_success 'move repo without breaking relative internal links' '
25+
test_when_finished rm -rf repo moved &&
26+
git init repo &&
27+
(
28+
cd repo &&
29+
test_commit initial &&
30+
git worktree add wt1 &&
31+
cd .. &&
32+
mv repo moved &&
33+
cd moved/wt1 &&
34+
git status >out 2>err &&
35+
test_must_be_empty err
36+
)
37+
'
38+
39+
test_done

0 commit comments

Comments
 (0)