Skip to content

Commit 92ca285

Browse files
committed
Merge branch 'cw/worktree-extension' into jch
* cw/worktree-extension: worktree: add `relativeWorktrees` extension setup: correctly reinitialize repository version worktree: add tests for worktrees with relative paths worktree: add `write_worktree_linking_files` function worktree: add CLI/config options for relative path linking
2 parents 50cf544 + b9fac09 commit 92ca285

17 files changed

+298
-127
lines changed

Documentation/config/extensions.txt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ Note that this setting should only be set by linkgit:git-init[1] or
6363
linkgit:git-clone[1]. Trying to change it after initialization will not
6464
work and will produce hard-to-diagnose issues.
6565

66+
relativeWorktrees::
67+
If enabled, indicates at least one worktree has been linked with
68+
relative paths. Automatically set if a worktree has been created or
69+
repaired with either the `--relative-paths` option or with the
70+
`worktree.useRelativePaths` config set to `true`.
71+
6672
worktreeConfig::
6773
If enabled, then worktrees will load config settings from the
6874
`$GIT_DIR/config.worktree` file in addition to the

Documentation/config/worktree.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,8 @@ worktree.guessRemote::
77
such a branch exists, it is checked out and set as "upstream"
88
for the new branch. If no such match can be found, it falls
99
back to creating a new branch from the current HEAD.
10+
worktree.useRelativePaths::
11+
If set to `true`, worktrees will be linked to the repository using
12+
relative paths rather than using absolute paths. This is particularly
13+
useful for setups where the repository and worktrees may be moved between
14+
different locations or environments.

Documentation/git-worktree.txt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,18 @@ To remove a locked worktree, specify `--force` twice.
216216
This can also be set up as the default behaviour by using the
217217
`worktree.guessRemote` config option.
218218

219+
--[no-]relative-paths::
220+
Worktrees will be linked to the repository using relative paths
221+
rather than using absolute paths. This is particularly useful for setups
222+
where the repository and worktrees may be moved between different
223+
locations or environments.
224+
+
225+
With `repair`, the linking files will be updated if there's an absolute/relative
226+
mismatch, even if the links are correct.
227+
+
228+
This can also be set up as the default behaviour by using the
229+
`worktree.useRelativePaths` config option.
230+
219231
--[no-]track::
220232
When creating a new branch, if `<commit-ish>` is a branch,
221233
mark it as "upstream" from the new branch. This is the

builtin/worktree.c

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,9 @@ static int git_worktree_config(const char *var, const char *value,
134134
if (!strcmp(var, "worktree.guessremote")) {
135135
guess_remote = git_config_bool(var, value);
136136
return 0;
137+
} else if (!strcmp(var, "worktree.userelativepaths")) {
138+
use_relative_paths = git_config_bool(var, value);
139+
return 0;
137140
}
138141

139142
return git_default_config(var, value, ctx, cb);
@@ -414,8 +417,7 @@ static int add_worktree(const char *path, const char *refname,
414417
const struct add_opts *opts)
415418
{
416419
struct strbuf sb_git = STRBUF_INIT, sb_repo = 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;
420+
struct strbuf sb = STRBUF_INIT;
419421
const char *name;
420422
struct strvec child_env = STRVEC_INIT;
421423
unsigned int counter = 0;
@@ -491,10 +493,7 @@ static int add_worktree(const char *path, const char *refname,
491493

492494
strbuf_reset(&sb);
493495
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
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));
496+
write_worktree_linking_files(sb_git, sb);
498497
strbuf_reset(&sb);
499498
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
500499
write_file(sb.buf, "../..");
@@ -578,12 +577,9 @@ static int add_worktree(const char *path, const char *refname,
578577

579578
strvec_clear(&child_env);
580579
strbuf_release(&sb);
581-
strbuf_release(&sb_tmp);
582580
strbuf_release(&symref);
583581
strbuf_release(&sb_repo);
584-
strbuf_release(&sb_repo_realpath);
585582
strbuf_release(&sb_git);
586-
strbuf_release(&sb_path_realpath);
587583
strbuf_release(&sb_name);
588584
free_worktree(wt);
589585
return ret;
@@ -796,6 +792,8 @@ static int add(int ac, const char **av, const char *prefix)
796792
PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
797793
OPT_BOOL(0, "guess-remote", &guess_remote,
798794
N_("try to match the new branch name with a remote-tracking branch")),
795+
OPT_BOOL(0, "relative-paths", &use_relative_paths,
796+
N_("use relative paths for worktrees")),
799797
OPT_END()
800798
};
801799
int ret;
@@ -1189,6 +1187,8 @@ static int move_worktree(int ac, const char **av, const char *prefix)
11891187
OPT__FORCE(&force,
11901188
N_("force move even if worktree is dirty or locked"),
11911189
PARSE_OPT_NOCOMPLETE),
1190+
OPT_BOOL(0, "relative-paths", &use_relative_paths,
1191+
N_("use relative paths for worktrees")),
11921192
OPT_END()
11931193
};
11941194
struct worktree **worktrees, *wt;
@@ -1382,6 +1382,8 @@ static int repair(int ac, const char **av, const char *prefix)
13821382
const char **p;
13831383
const char *self[] = { ".", NULL };
13841384
struct option options[] = {
1385+
OPT_BOOL(0, "relative-paths", &use_relative_paths,
1386+
N_("use relative paths for worktrees")),
13851387
OPT_END()
13861388
};
13871389
int rc = 0;

repository.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -283,6 +283,7 @@ int repo_init(struct repository *repo,
283283
repo_set_compat_hash_algo(repo, format.compat_hash_algo);
284284
repo_set_ref_storage_format(repo, format.ref_storage_format);
285285
repo->repository_format_worktree_config = format.worktree_config;
286+
repo->repository_format_relative_worktrees = format.relative_worktrees;
286287

287288
/* take ownership of format.partial_clone */
288289
repo->repository_format_partial_clone = format.partial_clone;

repository.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ struct repository {
150150

151151
/* Configurations */
152152
int repository_format_worktree_config;
153+
int repository_format_relative_worktrees;
153154

154155
/* Indicate if a repository has a different 'commondir' from 'gitdir' */
155156
unsigned different_commondir:1;

setup.c

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,9 @@ static enum extension_result handle_extension(const char *var,
683683
"extensions.refstorage", value);
684684
data->ref_storage_format = format;
685685
return EXTENSION_OK;
686+
} else if (!strcmp(ext, "relativeworktrees")) {
687+
data->relative_worktrees = git_config_bool(var, value);
688+
return EXTENSION_OK;
686689
}
687690
return EXTENSION_UNKNOWN;
688691
}
@@ -1854,6 +1857,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
18541857
repo_fmt.ref_storage_format);
18551858
the_repository->repository_format_worktree_config =
18561859
repo_fmt.worktree_config;
1860+
the_repository->repository_format_relative_worktrees =
1861+
repo_fmt.relative_worktrees;
18571862
/* take ownership of repo_fmt.partial_clone */
18581863
the_repository->repository_format_partial_clone =
18591864
repo_fmt.partial_clone;
@@ -1950,6 +1955,8 @@ void check_repository_format(struct repository_format *fmt)
19501955
fmt->ref_storage_format);
19511956
the_repository->repository_format_worktree_config =
19521957
fmt->worktree_config;
1958+
the_repository->repository_format_relative_worktrees =
1959+
fmt->relative_worktrees;
19531960
the_repository->repository_format_partial_clone =
19541961
xstrdup_or_null(fmt->partial_clone);
19551962
clear_repository_format(&repo_fmt);
@@ -2204,8 +2211,8 @@ void initialize_repository_version(int hash_algo,
22042211
enum ref_storage_format ref_storage_format,
22052212
int reinit)
22062213
{
2207-
char repo_version_string[10];
2208-
int repo_version = GIT_REPO_VERSION;
2214+
struct strbuf repo_version = STRBUF_INIT;
2215+
int target_version = GIT_REPO_VERSION;
22092216

22102217
/*
22112218
* Note that we initialize the repository version to 1 when the ref
@@ -2216,12 +2223,7 @@ void initialize_repository_version(int hash_algo,
22162223
*/
22172224
if (hash_algo != GIT_HASH_SHA1 ||
22182225
ref_storage_format != REF_STORAGE_FORMAT_FILES)
2219-
repo_version = GIT_REPO_VERSION_READ;
2220-
2221-
/* This forces creation of new config file */
2222-
xsnprintf(repo_version_string, sizeof(repo_version_string),
2223-
"%d", repo_version);
2224-
git_config_set("core.repositoryformatversion", repo_version_string);
2226+
target_version = GIT_REPO_VERSION_READ;
22252227

22262228
if (hash_algo != GIT_HASH_SHA1 && hash_algo != GIT_HASH_UNKNOWN)
22272229
git_config_set("extensions.objectformat",
@@ -2234,6 +2236,25 @@ void initialize_repository_version(int hash_algo,
22342236
ref_storage_format_to_name(ref_storage_format));
22352237
else if (reinit)
22362238
git_config_set_gently("extensions.refstorage", NULL);
2239+
2240+
if (reinit) {
2241+
struct strbuf config = STRBUF_INIT;
2242+
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
2243+
2244+
strbuf_git_common_path(&config, the_repository, "config");
2245+
read_repository_format(&repo_fmt, config.buf);
2246+
2247+
if (repo_fmt.v1_only_extensions.nr)
2248+
target_version = GIT_REPO_VERSION_READ;
2249+
2250+
strbuf_release(&config);
2251+
clear_repository_format(&repo_fmt);
2252+
}
2253+
2254+
strbuf_addf(&repo_version, "%d", target_version);
2255+
git_config_set("core.repositoryformatversion", repo_version.buf);
2256+
2257+
strbuf_release(&repo_version);
22372258
}
22382259

22392260
static int is_reinit(void)
@@ -2333,7 +2354,7 @@ static int create_default_files(const char *template_path,
23332354
adjust_shared_perm(repo_get_git_dir(the_repository));
23342355
}
23352356

2336-
initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, 0);
2357+
initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, reinit);
23372358

23382359
/* Check filemode trustability */
23392360
path = git_path_buf(&buf, "config");

setup.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ struct repository_format {
129129
int precious_objects;
130130
char *partial_clone; /* value of extensions.partialclone */
131131
int worktree_config;
132+
int relative_worktrees;
132133
int is_bare;
133134
int hash_algo;
134135
int compat_hash_algo;

t/t0001-init.sh

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -435,19 +435,28 @@ sep_git_dir_worktree () {
435435
test_when_finished "rm -rf mainwt linkwt seprepo" &&
436436
git init mainwt &&
437437
test_commit -C mainwt gumby &&
438+
git -C mainwt config worktree.useRelativePaths "$([ "$2" = "relative" ] && echo true || echo false)" &&
438439
git -C mainwt worktree add --detach ../linkwt &&
439440
git -C "$1" init --separate-git-dir ../seprepo &&
440441
git -C mainwt rev-parse --git-common-dir >expect &&
441442
git -C linkwt rev-parse --git-common-dir >actual &&
442443
test_cmp expect actual
443444
}
444445

445-
test_expect_success 're-init to move gitdir with linked worktrees' '
446-
sep_git_dir_worktree mainwt
446+
test_expect_success 're-init to move gitdir with linked worktrees (absolute)' '
447+
sep_git_dir_worktree mainwt absolute
447448
'
448449

449-
test_expect_success 're-init to move gitdir within linked worktree' '
450-
sep_git_dir_worktree linkwt
450+
test_expect_success 're-init to move gitdir within linked worktree (absolute)' '
451+
sep_git_dir_worktree linkwt absolute
452+
'
453+
454+
test_expect_success 're-init to move gitdir with linked worktrees (relative)' '
455+
sep_git_dir_worktree mainwt relative
456+
'
457+
458+
test_expect_success 're-init to move gitdir within linked worktree (relative)' '
459+
sep_git_dir_worktree linkwt relative
451460
'
452461

453462
test_expect_success MINGW '.git hidden' '

t/t2400-worktree-add.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1207,4 +1207,58 @@ test_expect_success '"add" with initialized submodule, with submodule.recurse se
12071207
git -C project-clone -c submodule.recurse worktree add ../project-5
12081208
'
12091209

1210+
test_expect_success 'can create worktrees with relative paths' '
1211+
test_when_finished "git worktree remove relative" &&
1212+
git config worktree.useRelativePaths false &&
1213+
git worktree add --relative-paths ./relative &&
1214+
cat relative/.git >actual &&
1215+
echo "gitdir: ../.git/worktrees/relative" >expect &&
1216+
test_cmp expect actual &&
1217+
cat .git/worktrees/relative/gitdir >actual &&
1218+
echo "../../../relative/.git" >expect &&
1219+
test_cmp expect actual
1220+
1221+
'
1222+
1223+
test_expect_success 'can create worktrees with absolute paths' '
1224+
git config worktree.useRelativePaths true &&
1225+
git worktree add ./relative &&
1226+
cat relative/.git >actual &&
1227+
echo "gitdir: ../.git/worktrees/relative" >expect &&
1228+
test_cmp expect actual &&
1229+
git worktree add --no-relative-paths ./absolute &&
1230+
cat absolute/.git >actual &&
1231+
echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
1232+
test_cmp expect actual
1233+
'
1234+
1235+
test_expect_success 'move repo without breaking relative internal links' '
1236+
test_when_finished rm -rf repo moved &&
1237+
git init repo &&
1238+
(
1239+
cd repo &&
1240+
git config worktree.useRelativePaths true &&
1241+
test_commit initial &&
1242+
git worktree add wt1 &&
1243+
cd .. &&
1244+
mv repo moved &&
1245+
cd moved/wt1 &&
1246+
git status >out 2>err &&
1247+
test_must_be_empty err
1248+
)
1249+
'
1250+
1251+
test_expect_success 'relative worktree sets extension config' '
1252+
test_when_finished "rm -rf repo" &&
1253+
git init repo &&
1254+
git -C repo commit --allow-empty -m base &&
1255+
git -C repo worktree add --relative-paths ./foo &&
1256+
git -C repo config get core.repositoryformatversion >actual &&
1257+
echo 1 >expected &&
1258+
test_cmp expected actual &&
1259+
git -C repo config get extensions.relativeworktrees >actual &&
1260+
echo true >expected &&
1261+
test_cmp expected actual
1262+
'
1263+
12101264
test_done

0 commit comments

Comments
 (0)