@@ -7,3 +7,13 @@ 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::
+ Link worktrees using relative paths (when "true") or absolute
+ paths (when "false"). This is particularly useful for setups
+ where the repository and worktrees may be moved between
+ different locations or environments. Defaults to "false".
++
+Note that setting `worktree.useRelativePaths` to "true" implies enabling the
+`extension.relativeWorktrees` config (see linkgit:git-config[1]),
+thus making it incompatible with older versions of Git.
@@ -216,6 +216,10 @@ 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::
+ Overrides the `worktree.useRelativePaths` config option, see
+ linkgit:git-config[1].
+
--[no-]track::
When creating a new branch, if `<commit-ish>` is a branch,
mark it as "upstream" from the new branch. This is the
@@ -120,12 +120,14 @@ struct add_opts {
int quiet;
int checkout;
int orphan;
+ int relative_paths;
const char *keep_locked;
};
static int show_only;
static int verbose;
static int guess_remote;
+static int use_relative_paths;
static timestamp_t expire;
static int git_worktree_config(const char *var, const char *value,
@@ -134,6 +136,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 +419,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 +495,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, opts->relative_paths);
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
@@ -578,12 +579,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,12 +794,15 @@ 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", &opts.relative_paths,
+ N_("use relative paths for worktrees")),
OPT_END()
};
int ret;
memset(&opts, 0, sizeof(opts));
opts.checkout = 1;
+ opts.relative_paths = use_relative_paths;
ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);
if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach");
@@ -1207,4 +1207,58 @@ 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_expect_success 'relative worktree sets extension config' '
+ test_when_finished "rm -rf repo" &&
+ git init repo &&
+ git -C repo commit --allow-empty -m base &&
+ git -C repo worktree add --relative-paths ./foo &&
+ git -C repo config get core.repositoryformatversion >actual &&
+ echo 1 >expected &&
+ test_cmp expected actual &&
+ git -C repo config get extensions.relativeworktrees >actual &&
+ echo true >expected &&
+ test_cmp expected actual
+'
+
test_done
@@ -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 &&
@@ -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 &&
deleted file mode 100755
@@ -1,39 +0,0 @@
-#!/bin/sh
-
-test_description='test worktrees linked with relative paths'
-
-TEST_PASSES_SANITIZE_LEAK=true
-. ./test-lib.sh
-
-test_expect_success 'links worktrees with relative paths' '
- test_when_finished rm -rf repo &&
- git init repo &&
- (
- cd repo &&
- test_commit initial &&
- git worktree add wt1 &&
- echo "../../../wt1/.git" >expected_gitdir &&
- cat .git/worktrees/wt1/gitdir >actual_gitdir &&
- echo "gitdir: ../.git/worktrees/wt1" >expected_git &&
- cat wt1/.git >actual_git &&
- test_cmp expected_gitdir actual_gitdir &&
- test_cmp expected_git actual_git
- )
-'
-
-test_expect_success 'move repo without breaking relative internal links' '
- test_when_finished rm -rf repo moved &&
- git init repo &&
- (
- cd repo &&
- 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
This introduces the `--[no-]relative-paths` CLI option and `worktree.useRelativePaths` configuration setting to the `worktree add` command. When enabled these options allow worktrees to be linked using relative paths, enhancing portability across environments where absolute paths may differ (e.g., containerized setups, shared network drives). Git still creates absolute paths by default, but these options allow users to opt-in to relative paths if desired. The t2408 test file is removed and more comprehensive tests are written for the various worktree operations in their own files. Signed-off-by: Caleb White <cdwhite3@pm.me> --- Documentation/config/worktree.txt | 10 ++++++++ Documentation/git-worktree.txt | 4 +++ builtin/worktree.c | 19 +++++++------- t/t2400-worktree-add.sh | 54 +++++++++++++++++++++++++++++++++++++++ t/t2401-worktree-prune.sh | 3 ++- t/t2402-worktree-list.sh | 22 ++++++++++++++++ t/t2408-worktree-relative.sh | 39 ---------------------------- 7 files changed, 102 insertions(+), 49 deletions(-)