Git fork

Merge branch 'cw/worktree-extension'

Introduce a new repository extension to prevent older Git versions
from mis-interpreting worktrees created with relative paths.

* cw/worktree-extension:
worktree: refactor `repair_worktree_after_gitdir_move()`
worktree: add relative cli/config options to `repair` command
worktree: add relative cli/config options to `move` command
worktree: add relative cli/config options to `add` command
worktree: add `write_worktree_linking_files()` function
worktree: refactor infer_backlink return
worktree: add `relativeWorktrees` extension
setup: correctly reinitialize repository version

+334 -141
+6
Documentation/config/extensions.txt
··· 63 linkgit:git-clone[1]. Trying to change it after initialization will not 64 work and will produce hard-to-diagnose issues. 65 66 worktreeConfig:: 67 If enabled, then worktrees will load config settings from the 68 `$GIT_DIR/config.worktree` file in addition to the
··· 63 linkgit:git-clone[1]. Trying to change it after initialization will not 64 work and will produce hard-to-diagnose issues. 65 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 + 72 worktreeConfig:: 73 If enabled, then worktrees will load config settings from the 74 `$GIT_DIR/config.worktree` file in addition to the
+10
Documentation/config/worktree.txt
··· 7 such a branch exists, it is checked out and set as "upstream" 8 for the new branch. If no such match can be found, it falls 9 back to creating a new branch from the current HEAD.
··· 7 such a branch exists, it is checked out and set as "upstream" 8 for the new branch. If no such match can be found, it falls 9 back to creating a new branch from the current HEAD. 10 + 11 + worktree.useRelativePaths:: 12 + Link worktrees using relative paths (when "true") or absolute 13 + paths (when "false"). This is particularly useful for setups 14 + where the repository and worktrees may be moved between 15 + different locations or environments. Defaults to "false". 16 + + 17 + Note that setting `worktree.useRelativePaths` to "true" implies enabling the 18 + `extension.relativeWorktrees` config (see linkgit:git-config[1]), 19 + thus making it incompatible with older versions of Git.
+8
Documentation/git-worktree.txt
··· 216 This can also be set up as the default behaviour by using the 217 `worktree.guessRemote` config option. 218 219 --[no-]track:: 220 When creating a new branch, if `<commit-ish>` is a branch, 221 mark it as "upstream" from the new branch. This is the
··· 216 This can also be set up as the default behaviour by using the 217 `worktree.guessRemote` config option. 218 219 + --[no-]relative-paths:: 220 + Link worktrees using relative paths or absolute paths (default). 221 + Overrides the `worktree.useRelativePaths` config option, see 222 + linkgit:git-config[1]. 223 + + 224 + With `repair`, the linking files will be updated if there's an absolute/relative 225 + mismatch, even if the links are correct. 226 + 227 --[no-]track:: 228 When creating a new branch, if `<commit-ish>` is a branch, 229 mark it as "upstream" from the new branch. This is the
+17 -12
builtin/worktree.c
··· 120 int quiet; 121 int checkout; 122 int orphan; 123 const char *keep_locked; 124 }; 125 126 static int show_only; 127 static int verbose; 128 static int guess_remote; 129 static timestamp_t expire; 130 131 static int git_worktree_config(const char *var, const char *value, ··· 133 { 134 if (!strcmp(var, "worktree.guessremote")) { 135 guess_remote = git_config_bool(var, value); 136 return 0; 137 } 138 ··· 415 const struct add_opts *opts) 416 { 417 struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; 418 - struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT; 419 - struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT; 420 const char *name; 421 struct strvec child_env = STRVEC_INIT; 422 unsigned int counter = 0; ··· 492 493 strbuf_reset(&sb); 494 strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); 495 - strbuf_realpath(&sb_path_realpath, path, 1); 496 - strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1); 497 - write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp)); 498 - write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp)); 499 strbuf_reset(&sb); 500 strbuf_addf(&sb, "%s/commondir", sb_repo.buf); 501 write_file(sb.buf, "../.."); ··· 579 580 strvec_clear(&child_env); 581 strbuf_release(&sb); 582 - strbuf_release(&sb_tmp); 583 strbuf_release(&symref); 584 strbuf_release(&sb_repo); 585 - strbuf_release(&sb_repo_realpath); 586 strbuf_release(&sb_git); 587 - strbuf_release(&sb_path_realpath); 588 strbuf_release(&sb_name); 589 free_worktree(wt); 590 return ret; ··· 798 PARSE_OPT_NOARG | PARSE_OPT_OPTARG), 799 OPT_BOOL(0, "guess-remote", &guess_remote, 800 N_("try to match the new branch name with a remote-tracking branch")), 801 OPT_END() 802 }; 803 int ret; 804 805 memset(&opts, 0, sizeof(opts)); 806 opts.checkout = 1; 807 ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0); 808 if (!!opts.detach + !!new_branch + !!new_branch_force > 1) 809 die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); ··· 1195 OPT__FORCE(&force, 1196 N_("force move even if worktree is dirty or locked"), 1197 PARSE_OPT_NOCOMPLETE), 1198 OPT_END() 1199 }; 1200 struct worktree **worktrees, *wt; ··· 1247 if (rename(wt->path, dst.buf) == -1) 1248 die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf); 1249 1250 - update_worktree_location(wt, dst.buf); 1251 1252 strbuf_release(&dst); 1253 free_worktrees(worktrees); ··· 1390 const char **p; 1391 const char *self[] = { ".", NULL }; 1392 struct option options[] = { 1393 OPT_END() 1394 }; 1395 int rc = 0; ··· 1397 ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0); 1398 p = ac > 0 ? av : self; 1399 for (; *p; p++) 1400 - repair_worktree_at_path(*p, report_repair, &rc); 1401 - repair_worktrees(report_repair, &rc); 1402 return rc; 1403 } 1404
··· 120 int quiet; 121 int checkout; 122 int orphan; 123 + int relative_paths; 124 const char *keep_locked; 125 }; 126 127 static int show_only; 128 static int verbose; 129 static int guess_remote; 130 + static int use_relative_paths; 131 static timestamp_t expire; 132 133 static int git_worktree_config(const char *var, const char *value, ··· 135 { 136 if (!strcmp(var, "worktree.guessremote")) { 137 guess_remote = git_config_bool(var, value); 138 + return 0; 139 + } else if (!strcmp(var, "worktree.userelativepaths")) { 140 + use_relative_paths = git_config_bool(var, value); 141 return 0; 142 } 143 ··· 420 const struct add_opts *opts) 421 { 422 struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT; 423 + struct strbuf sb = STRBUF_INIT; 424 const char *name; 425 struct strvec child_env = STRVEC_INIT; 426 unsigned int counter = 0; ··· 496 497 strbuf_reset(&sb); 498 strbuf_addf(&sb, "%s/gitdir", sb_repo.buf); 499 + write_worktree_linking_files(sb_git, sb, opts->relative_paths); 500 strbuf_reset(&sb); 501 strbuf_addf(&sb, "%s/commondir", sb_repo.buf); 502 write_file(sb.buf, "../.."); ··· 580 581 strvec_clear(&child_env); 582 strbuf_release(&sb); 583 strbuf_release(&symref); 584 strbuf_release(&sb_repo); 585 strbuf_release(&sb_git); 586 strbuf_release(&sb_name); 587 free_worktree(wt); 588 return ret; ··· 796 PARSE_OPT_NOARG | PARSE_OPT_OPTARG), 797 OPT_BOOL(0, "guess-remote", &guess_remote, 798 N_("try to match the new branch name with a remote-tracking branch")), 799 + OPT_BOOL(0, "relative-paths", &opts.relative_paths, 800 + N_("use relative paths for worktrees")), 801 OPT_END() 802 }; 803 int ret; 804 805 memset(&opts, 0, sizeof(opts)); 806 opts.checkout = 1; 807 + opts.relative_paths = use_relative_paths; 808 ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0); 809 if (!!opts.detach + !!new_branch + !!new_branch_force > 1) 810 die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach"); ··· 1196 OPT__FORCE(&force, 1197 N_("force move even if worktree is dirty or locked"), 1198 PARSE_OPT_NOCOMPLETE), 1199 + OPT_BOOL(0, "relative-paths", &use_relative_paths, 1200 + N_("use relative paths for worktrees")), 1201 OPT_END() 1202 }; 1203 struct worktree **worktrees, *wt; ··· 1250 if (rename(wt->path, dst.buf) == -1) 1251 die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf); 1252 1253 + update_worktree_location(wt, dst.buf, use_relative_paths); 1254 1255 strbuf_release(&dst); 1256 free_worktrees(worktrees); ··· 1393 const char **p; 1394 const char *self[] = { ".", NULL }; 1395 struct option options[] = { 1396 + OPT_BOOL(0, "relative-paths", &use_relative_paths, 1397 + N_("use relative paths for worktrees")), 1398 OPT_END() 1399 }; 1400 int rc = 0; ··· 1402 ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0); 1403 p = ac > 0 ? av : self; 1404 for (; *p; p++) 1405 + repair_worktree_at_path(*p, report_repair, &rc, use_relative_paths); 1406 + repair_worktrees(report_repair, &rc, use_relative_paths); 1407 return rc; 1408 } 1409
+1
repository.c
··· 283 repo_set_compat_hash_algo(repo, format.compat_hash_algo); 284 repo_set_ref_storage_format(repo, format.ref_storage_format); 285 repo->repository_format_worktree_config = format.worktree_config; 286 287 /* take ownership of format.partial_clone */ 288 repo->repository_format_partial_clone = format.partial_clone;
··· 283 repo_set_compat_hash_algo(repo, format.compat_hash_algo); 284 repo_set_ref_storage_format(repo, format.ref_storage_format); 285 repo->repository_format_worktree_config = format.worktree_config; 286 + repo->repository_format_relative_worktrees = format.relative_worktrees; 287 288 /* take ownership of format.partial_clone */ 289 repo->repository_format_partial_clone = format.partial_clone;
+1
repository.h
··· 150 151 /* Configurations */ 152 int repository_format_worktree_config; 153 154 /* Indicate if a repository has a different 'commondir' from 'gitdir' */ 155 unsigned different_commondir:1;
··· 150 151 /* Configurations */ 152 int repository_format_worktree_config; 153 + int repository_format_relative_worktrees; 154 155 /* Indicate if a repository has a different 'commondir' from 'gitdir' */ 156 unsigned different_commondir:1;
+30 -9
setup.c
··· 683 "extensions.refstorage", value); 684 data->ref_storage_format = format; 685 return EXTENSION_OK; 686 } 687 return EXTENSION_UNKNOWN; 688 } ··· 1854 repo_fmt.ref_storage_format); 1855 the_repository->repository_format_worktree_config = 1856 repo_fmt.worktree_config; 1857 /* take ownership of repo_fmt.partial_clone */ 1858 the_repository->repository_format_partial_clone = 1859 repo_fmt.partial_clone; ··· 1950 fmt->ref_storage_format); 1951 the_repository->repository_format_worktree_config = 1952 fmt->worktree_config; 1953 the_repository->repository_format_partial_clone = 1954 xstrdup_or_null(fmt->partial_clone); 1955 clear_repository_format(&repo_fmt); ··· 2204 enum ref_storage_format ref_storage_format, 2205 int reinit) 2206 { 2207 - char repo_version_string[10]; 2208 - int repo_version = GIT_REPO_VERSION; 2209 2210 /* 2211 * Note that we initialize the repository version to 1 when the ref ··· 2216 */ 2217 if (hash_algo != GIT_HASH_SHA1 || 2218 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); 2225 2226 if (hash_algo != GIT_HASH_SHA1 && hash_algo != GIT_HASH_UNKNOWN) 2227 git_config_set("extensions.objectformat", ··· 2234 ref_storage_format_to_name(ref_storage_format)); 2235 else if (reinit) 2236 git_config_set_gently("extensions.refstorage", NULL); 2237 } 2238 2239 static int is_reinit(void) ··· 2333 adjust_shared_perm(repo_get_git_dir(the_repository)); 2334 } 2335 2336 - initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, 0); 2337 2338 /* Check filemode trustability */ 2339 path = git_path_buf(&buf, "config");
··· 683 "extensions.refstorage", value); 684 data->ref_storage_format = format; 685 return EXTENSION_OK; 686 + } else if (!strcmp(ext, "relativeworktrees")) { 687 + data->relative_worktrees = git_config_bool(var, value); 688 + return EXTENSION_OK; 689 } 690 return EXTENSION_UNKNOWN; 691 } ··· 1857 repo_fmt.ref_storage_format); 1858 the_repository->repository_format_worktree_config = 1859 repo_fmt.worktree_config; 1860 + the_repository->repository_format_relative_worktrees = 1861 + repo_fmt.relative_worktrees; 1862 /* take ownership of repo_fmt.partial_clone */ 1863 the_repository->repository_format_partial_clone = 1864 repo_fmt.partial_clone; ··· 1955 fmt->ref_storage_format); 1956 the_repository->repository_format_worktree_config = 1957 fmt->worktree_config; 1958 + the_repository->repository_format_relative_worktrees = 1959 + fmt->relative_worktrees; 1960 the_repository->repository_format_partial_clone = 1961 xstrdup_or_null(fmt->partial_clone); 1962 clear_repository_format(&repo_fmt); ··· 2211 enum ref_storage_format ref_storage_format, 2212 int reinit) 2213 { 2214 + struct strbuf repo_version = STRBUF_INIT; 2215 + int target_version = GIT_REPO_VERSION; 2216 2217 /* 2218 * Note that we initialize the repository version to 1 when the ref ··· 2223 */ 2224 if (hash_algo != GIT_HASH_SHA1 || 2225 ref_storage_format != REF_STORAGE_FORMAT_FILES) 2226 + target_version = GIT_REPO_VERSION_READ; 2227 2228 if (hash_algo != GIT_HASH_SHA1 && hash_algo != GIT_HASH_UNKNOWN) 2229 git_config_set("extensions.objectformat", ··· 2236 ref_storage_format_to_name(ref_storage_format)); 2237 else if (reinit) 2238 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); 2258 } 2259 2260 static int is_reinit(void) ··· 2354 adjust_shared_perm(repo_get_git_dir(the_repository)); 2355 } 2356 2357 + initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, reinit); 2358 2359 /* Check filemode trustability */ 2360 path = git_path_buf(&buf, "config");
+1
setup.h
··· 129 int precious_objects; 130 char *partial_clone; /* value of extensions.partialclone */ 131 int worktree_config; 132 int is_bare; 133 int hash_algo; 134 int compat_hash_algo;
··· 129 int precious_objects; 130 char *partial_clone; /* value of extensions.partialclone */ 131 int worktree_config; 132 + int relative_worktrees; 133 int is_bare; 134 int hash_algo; 135 int compat_hash_algo;
+18 -4
t/t0001-init.sh
··· 433 sep_git_dir_worktree () { 434 test_when_finished "rm -rf mainwt linkwt seprepo" && 435 git init mainwt && 436 test_commit -C mainwt gumby && 437 git -C mainwt worktree add --detach ../linkwt && 438 git -C "$1" init --separate-git-dir ../seprepo && ··· 441 test_cmp expect actual 442 } 443 444 - test_expect_success 're-init to move gitdir with linked worktrees' ' 445 - sep_git_dir_worktree mainwt 446 ' 447 448 - test_expect_success 're-init to move gitdir within linked worktree' ' 449 - sep_git_dir_worktree linkwt 450 ' 451 452 test_expect_success MINGW '.git hidden' '
··· 433 sep_git_dir_worktree () { 434 test_when_finished "rm -rf mainwt linkwt seprepo" && 435 git init mainwt && 436 + if test "relative" = $2 437 + then 438 + test_config -C mainwt worktree.useRelativePaths true 439 + else 440 + test_config -C mainwt worktree.useRelativePaths false 441 + fi 442 test_commit -C mainwt gumby && 443 git -C mainwt worktree add --detach ../linkwt && 444 git -C "$1" init --separate-git-dir ../seprepo && ··· 447 test_cmp expect actual 448 } 449 450 + test_expect_success 're-init to move gitdir with linked worktrees (absolute)' ' 451 + sep_git_dir_worktree mainwt absolute 452 ' 453 454 + test_expect_success 're-init to move gitdir within linked worktree (absolute)' ' 455 + sep_git_dir_worktree linkwt absolute 456 + ' 457 + 458 + test_expect_success 're-init to move gitdir with linked worktrees (relative)' ' 459 + sep_git_dir_worktree mainwt relative 460 + ' 461 + 462 + test_expect_success 're-init to move gitdir within linked worktree (relative)' ' 463 + sep_git_dir_worktree linkwt relative 464 ' 465 466 test_expect_success MINGW '.git hidden' '
+46
t/t2400-worktree-add.sh
··· 1206 git -C project-clone -c submodule.recurse worktree add ../project-5 1207 ' 1208 1209 test_done
··· 1206 git -C project-clone -c submodule.recurse worktree add ../project-5 1207 ' 1208 1209 + test_expect_success 'can create worktrees with relative paths' ' 1210 + test_when_finished "git worktree remove relative" && 1211 + test_config worktree.useRelativePaths false && 1212 + git worktree add --relative-paths ./relative && 1213 + echo "gitdir: ../.git/worktrees/relative" >expect && 1214 + test_cmp expect relative/.git && 1215 + echo "../../../relative/.git" >expect && 1216 + test_cmp expect .git/worktrees/relative/gitdir 1217 + ' 1218 + 1219 + test_expect_success 'can create worktrees with absolute paths' ' 1220 + test_config worktree.useRelativePaths true && 1221 + git worktree add ./relative && 1222 + echo "gitdir: ../.git/worktrees/relative" >expect && 1223 + test_cmp expect relative/.git && 1224 + git worktree add --no-relative-paths ./absolute && 1225 + echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect && 1226 + test_cmp expect absolute/.git && 1227 + echo "$(pwd)/absolute/.git" >expect && 1228 + test_cmp expect .git/worktrees/absolute/gitdir 1229 + ' 1230 + 1231 + test_expect_success 'move repo without breaking relative internal links' ' 1232 + test_when_finished rm -rf repo moved && 1233 + git init repo && 1234 + ( 1235 + cd repo && 1236 + test_commit initial && 1237 + git worktree add --relative-paths wt1 && 1238 + cd .. && 1239 + mv repo moved && 1240 + cd moved/wt1 && 1241 + git worktree list >out 2>err && 1242 + test_must_be_empty err 1243 + ) 1244 + ' 1245 + 1246 + test_expect_success 'relative worktree sets extension config' ' 1247 + test_when_finished "rm -rf repo" && 1248 + git init repo && 1249 + git -C repo commit --allow-empty -m base && 1250 + git -C repo worktree add --relative-paths ./foo && 1251 + test_cmp_config -C repo 1 core.repositoryformatversion && 1252 + test_cmp_config -C repo true extensions.relativeworktrees 1253 + ' 1254 + 1255 test_done
+2 -1
t/t2401-worktree-prune.sh
··· 119 ! test -d .git/worktrees/wt 120 ' 121 122 - test_expect_success 'not prune proper worktrees when run inside linked worktree' ' 123 test_when_finished rm -rf repo wt_ext && 124 git init repo && 125 ( 126 cd repo && 127 echo content >file && 128 git add file && 129 git commit -m msg &&
··· 119 ! test -d .git/worktrees/wt 120 ' 121 122 + test_expect_success 'not prune proper worktrees inside linked worktree with relative paths' ' 123 test_when_finished rm -rf repo wt_ext && 124 git init repo && 125 ( 126 cd repo && 127 + git config worktree.useRelativePaths true && 128 echo content >file && 129 git add file && 130 git commit -m msg &&
+22
t/t2402-worktree-list.sh
··· 260 ' 261 262 test_expect_success 'linked worktrees are sorted' ' 263 mkdir sorted && 264 git init sorted/main && 265 ( ··· 267 test_tick && 268 test_commit new && 269 git worktree add ../first && 270 git worktree add ../second && 271 git worktree list --porcelain >out && 272 grep ^worktree out >actual
··· 260 ' 261 262 test_expect_success 'linked worktrees are sorted' ' 263 + test_when_finished "rm -rf sorted" && 264 mkdir sorted && 265 git init sorted/main && 266 ( ··· 268 test_tick && 269 test_commit new && 270 git worktree add ../first && 271 + git worktree add ../second && 272 + git worktree list --porcelain >out && 273 + grep ^worktree out >actual 274 + ) && 275 + cat >expected <<-EOF && 276 + worktree $(pwd)/sorted/main 277 + worktree $(pwd)/sorted/first 278 + worktree $(pwd)/sorted/second 279 + EOF 280 + test_cmp expected sorted/main/actual 281 + ' 282 + 283 + test_expect_success 'linked worktrees with relative paths are shown with absolute paths' ' 284 + test_when_finished "rm -rf sorted" && 285 + mkdir sorted && 286 + git init sorted/main && 287 + ( 288 + cd sorted/main && 289 + test_tick && 290 + test_commit new && 291 + git worktree add --relative-paths ../first && 292 git worktree add ../second && 293 git worktree list --porcelain >out && 294 grep ^worktree out >actual
+25
t/t2403-worktree-move.sh
··· 246 ) 247 ' 248 249 test_done
··· 246 ) 247 ' 248 249 + test_expect_success 'move worktree with absolute path to relative path' ' 250 + test_config worktree.useRelativePaths false && 251 + git worktree add ./absolute && 252 + git worktree move --relative-paths absolute relative && 253 + echo "gitdir: ../.git/worktrees/absolute" >expect && 254 + test_cmp expect relative/.git && 255 + echo "../../../relative/.git" >expect && 256 + test_cmp expect .git/worktrees/absolute/gitdir && 257 + test_config worktree.useRelativePaths true && 258 + git worktree move relative relative2 && 259 + echo "gitdir: ../.git/worktrees/absolute" >expect && 260 + test_cmp expect relative2/.git && 261 + echo "../../../relative2/.git" >expect && 262 + test_cmp expect .git/worktrees/absolute/gitdir 263 + ' 264 + 265 + test_expect_success 'move worktree with relative path to absolute path' ' 266 + test_config worktree.useRelativePaths true && 267 + git worktree move --no-relative-paths relative2 absolute && 268 + echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect && 269 + test_cmp expect absolute/.git && 270 + echo "$(pwd)/absolute/.git" >expect && 271 + test_cmp expect .git/worktrees/absolute/gitdir 272 + ' 273 + 274 test_done
+39
t/t2406-worktree-repair.sh
··· 215 test_cmp dup/linked.expect dup/linked/.git 216 ' 217 218 test_done
··· 215 test_cmp dup/linked.expect dup/linked/.git 216 ' 217 218 + test_expect_success 'repair worktree with relative path with missing gitfile' ' 219 + test_when_finished "rm -rf main wt" && 220 + test_create_repo main && 221 + git -C main config worktree.useRelativePaths true && 222 + test_commit -C main init && 223 + git -C main worktree add --detach ../wt && 224 + rm wt/.git && 225 + test_path_is_missing wt/.git && 226 + git -C main worktree repair && 227 + echo "gitdir: ../main/.git/worktrees/wt" >expect && 228 + test_cmp expect wt/.git 229 + ' 230 + 231 + test_expect_success 'repair absolute worktree to use relative paths' ' 232 + test_when_finished "rm -rf main side sidemoved" && 233 + test_create_repo main && 234 + test_commit -C main init && 235 + git -C main worktree add --detach ../side && 236 + echo "../../../../sidemoved/.git" >expect-gitdir && 237 + echo "gitdir: ../main/.git/worktrees/side" >expect-gitfile && 238 + mv side sidemoved && 239 + git -C main worktree repair --relative-paths ../sidemoved && 240 + test_cmp expect-gitdir main/.git/worktrees/side/gitdir && 241 + test_cmp expect-gitfile sidemoved/.git 242 + ' 243 + 244 + test_expect_success 'repair relative worktree to use absolute paths' ' 245 + test_when_finished "rm -rf main side sidemoved" && 246 + test_create_repo main && 247 + test_commit -C main init && 248 + git -C main worktree add --relative-paths --detach ../side && 249 + echo "$(pwd)/sidemoved/.git" >expect-gitdir && 250 + echo "gitdir: $(pwd)/main/.git/worktrees/side" >expect-gitfile && 251 + mv side sidemoved && 252 + git -C main worktree repair ../sidemoved && 253 + test_cmp expect-gitdir main/.git/worktrees/side/gitdir && 254 + test_cmp expect-gitfile sidemoved/.git 255 + ' 256 + 257 test_done
-38
t/t2408-worktree-relative.sh
··· 1 - #!/bin/sh 2 - 3 - test_description='test worktrees linked with relative paths' 4 - 5 - . ./test-lib.sh 6 - 7 - test_expect_success 'links worktrees with relative paths' ' 8 - test_when_finished rm -rf repo && 9 - git init repo && 10 - ( 11 - cd repo && 12 - test_commit initial && 13 - git worktree add wt1 && 14 - echo "../../../wt1/.git" >expected_gitdir && 15 - cat .git/worktrees/wt1/gitdir >actual_gitdir && 16 - echo "gitdir: ../.git/worktrees/wt1" >expected_git && 17 - cat wt1/.git >actual_git && 18 - test_cmp expected_gitdir actual_gitdir && 19 - test_cmp expected_git actual_git 20 - ) 21 - ' 22 - 23 - test_expect_success 'move repo without breaking relative internal links' ' 24 - test_when_finished rm -rf repo moved && 25 - git init repo && 26 - ( 27 - cd repo && 28 - test_commit initial && 29 - git worktree add wt1 && 30 - cd .. && 31 - mv repo moved && 32 - cd moved/wt1 && 33 - git status >out 2>err && 34 - test_must_be_empty err 35 - ) 36 - ' 37 - 38 - test_done
···
+3 -3
t/t5504-fetch-receive-strict.sh
··· 170 test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err && 171 test_grep "could not open.*: does-not-exist" err && 172 test_must_fail git -c fsck.skipList=.git/config -c fsck.missingEmail=ignore fsck 2>err && 173 - test_grep "invalid object name: \[core\]" err 174 ' 175 176 test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' ' ··· 233 test_grep "could not open.*: does-not-exist" err && 234 git --git-dir=dst/.git config receive.fsck.skipList config && 235 test_must_fail git push --porcelain dst bogus 2>err && 236 - test_grep "invalid object name: \[core\]" err && 237 238 git --git-dir=dst/.git config receive.fsck.skipList SKIP && 239 git push --porcelain dst bogus ··· 262 test_grep "could not open.*: does-not-exist" err && 263 git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/config && 264 test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err && 265 - test_grep "invalid object name: \[core\]" err && 266 267 git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/SKIP && 268 git --git-dir=dst/.git fetch "file://$(pwd)" $refspec
··· 170 test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err && 171 test_grep "could not open.*: does-not-exist" err && 172 test_must_fail git -c fsck.skipList=.git/config -c fsck.missingEmail=ignore fsck 2>err && 173 + test_grep "invalid object name: " err 174 ' 175 176 test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' ' ··· 233 test_grep "could not open.*: does-not-exist" err && 234 git --git-dir=dst/.git config receive.fsck.skipList config && 235 test_must_fail git push --porcelain dst bogus 2>err && 236 + test_grep "invalid object name: " err && 237 238 git --git-dir=dst/.git config receive.fsck.skipList SKIP && 239 git push --porcelain dst bogus ··· 262 test_grep "could not open.*: does-not-exist" err && 263 git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/config && 264 test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err && 265 + test_grep "invalid object name: " err && 266 267 git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/SKIP && 268 git --git-dir=dst/.git fetch "file://$(pwd)" $refspec
+87 -70
worktree.c
··· 111 strbuf_strip_suffix(&worktree_path, "/.git"); 112 113 if (!is_absolute_path(worktree_path.buf)) { 114 - strbuf_strip_suffix(&path, "gitdir"); 115 - strbuf_addbuf(&path, &worktree_path); 116 - strbuf_realpath_forgiving(&worktree_path, path.buf, 0); 117 } 118 119 CALLOC_ARRAY(worktree, 1); ··· 376 return ret; 377 } 378 379 - void update_worktree_location(struct worktree *wt, const char *path_) 380 { 381 struct strbuf path = STRBUF_INIT; 382 - struct strbuf repo = STRBUF_INIT; 383 - struct strbuf file = STRBUF_INIT; 384 - struct strbuf tmp = STRBUF_INIT; 385 386 if (is_main_worktree(wt)) 387 BUG("can't relocate main worktree"); 388 389 - strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); 390 strbuf_realpath(&path, path_, 1); 391 if (fspathcmp(wt->path, path.buf)) { 392 - strbuf_addf(&file, "%s/gitdir", repo.buf); 393 - write_file(file.buf, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); 394 - strbuf_reset(&file); 395 - strbuf_addf(&file, "%s/.git", path.buf); 396 - write_file(file.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); 397 398 free(wt->path); 399 wt->path = strbuf_detach(&path, NULL); 400 } 401 strbuf_release(&path); 402 - strbuf_release(&repo); 403 - strbuf_release(&file); 404 - strbuf_release(&tmp); 405 } 406 407 int is_worktree_being_rebased(const struct worktree *wt, ··· 577 * pointing at <repo>/worktrees/<id>. 578 */ 579 static void repair_gitfile(struct worktree *wt, 580 - worktree_repair_fn fn, void *cb_data) 581 { 582 struct strbuf dotgit = STRBUF_INIT; 583 struct strbuf repo = STRBUF_INIT; 584 struct strbuf backlink = STRBUF_INIT; 585 - struct strbuf tmp = STRBUF_INIT; 586 char *dotgit_contents = NULL; 587 const char *repair = NULL; 588 int err; ··· 598 599 strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); 600 strbuf_addf(&dotgit, "%s/.git", wt->path); 601 dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); 602 603 if (dotgit_contents) { ··· 615 repair = _(".git file broken"); 616 else if (fspathcmp(backlink.buf, repo.buf)) 617 repair = _(".git file incorrect"); 618 619 if (repair) { 620 fn(0, wt->path, repair, cb_data); 621 - write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, wt->path, &tmp)); 622 } 623 624 done: 625 free(dotgit_contents); 626 strbuf_release(&repo); 627 strbuf_release(&dotgit); 628 strbuf_release(&backlink); 629 - strbuf_release(&tmp); 630 } 631 632 static void repair_noop(int iserr UNUSED, ··· 637 /* nothing */ 638 } 639 640 - void repair_worktrees(worktree_repair_fn fn, void *cb_data) 641 { 642 struct worktree **worktrees = get_worktrees_internal(1); 643 struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ ··· 645 if (!fn) 646 fn = repair_noop; 647 for (; *wt; wt++) 648 - repair_gitfile(*wt, fn, cb_data); 649 free_worktrees(worktrees); 650 } 651 652 void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path) 653 { 654 - struct strbuf path = STRBUF_INIT; 655 - struct strbuf repo = STRBUF_INIT; 656 struct strbuf gitdir = STRBUF_INIT; 657 struct strbuf dotgit = STRBUF_INIT; 658 - struct strbuf olddotgit = STRBUF_INIT; 659 - struct strbuf tmp = STRBUF_INIT; 660 661 if (is_main_worktree(wt)) 662 goto done; 663 664 - strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); 665 - strbuf_addf(&gitdir, "%s/gitdir", repo.buf); 666 667 - if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) 668 goto done; 669 670 - strbuf_rtrim(&olddotgit); 671 - if (is_absolute_path(olddotgit.buf)) { 672 - strbuf_addbuf(&dotgit, &olddotgit); 673 - } else { 674 - strbuf_addf(&dotgit, "%s/worktrees/%s/%s", old_path, wt->id, olddotgit.buf); 675 strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0); 676 } 677 678 if (!file_exists(dotgit.buf)) 679 goto done; 680 681 - strbuf_addbuf(&path, &dotgit); 682 - strbuf_strip_suffix(&path, "/.git"); 683 - 684 - write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); 685 - write_file(gitdir.buf, "%s", relative_path(dotgit.buf, repo.buf, &tmp)); 686 done: 687 - strbuf_release(&path); 688 - strbuf_release(&repo); 689 strbuf_release(&gitdir); 690 strbuf_release(&dotgit); 691 - strbuf_release(&olddotgit); 692 - strbuf_release(&tmp); 693 } 694 695 void repair_worktrees_after_gitdir_move(const char *old_path) ··· 725 * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may 726 * be able to infer the gitdir by manually reading /path/to/worktree/.git, 727 * extracting the <id>, and checking if <repo>/worktrees/<id> exists. 728 */ 729 - static int infer_backlink(const char *gitfile, struct strbuf *inferred) 730 { 731 struct strbuf actual = STRBUF_INIT; 732 const char *id; ··· 747 goto error; 748 749 strbuf_release(&actual); 750 - return 1; 751 - 752 error: 753 strbuf_release(&actual); 754 strbuf_reset(inferred); /* clear invalid path */ 755 - return 0; 756 } 757 758 /* ··· 760 * the worktree's path. 761 */ 762 void repair_worktree_at_path(const char *path, 763 - worktree_repair_fn fn, void *cb_data) 764 { 765 struct strbuf dotgit = STRBUF_INIT; 766 - struct strbuf realdotgit = STRBUF_INIT; 767 struct strbuf backlink = STRBUF_INIT; 768 struct strbuf inferred_backlink = STRBUF_INIT; 769 struct strbuf gitdir = STRBUF_INIT; 770 struct strbuf olddotgit = STRBUF_INIT; 771 - struct strbuf realolddotgit = STRBUF_INIT; 772 - struct strbuf tmp = STRBUF_INIT; 773 char *dotgit_contents = NULL; 774 const char *repair = NULL; 775 int err; ··· 781 goto done; 782 783 strbuf_addf(&dotgit, "%s/.git", path); 784 - if (!strbuf_realpath(&realdotgit, dotgit.buf, 0)) { 785 fn(1, path, _("not a valid path"), cb_data); 786 goto done; 787 } 788 789 - infer_backlink(realdotgit.buf, &inferred_backlink); 790 strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0); 791 - dotgit_contents = xstrdup_or_null(read_gitfile_gently(realdotgit.buf, &err)); 792 if (dotgit_contents) { 793 if (is_absolute_path(dotgit_contents)) { 794 strbuf_addstr(&backlink, dotgit_contents); 795 } else { 796 - strbuf_addbuf(&backlink, &realdotgit); 797 strbuf_strip_suffix(&backlink, ".git"); 798 strbuf_addstr(&backlink, dotgit_contents); 799 strbuf_realpath_forgiving(&backlink, backlink.buf, 0); 800 } 801 } else if (err == READ_GITFILE_ERR_NOT_A_FILE) { 802 - fn(1, realdotgit.buf, _("unable to locate repository; .git is not a file"), cb_data); 803 goto done; 804 } else if (err == READ_GITFILE_ERR_NOT_A_REPO) { 805 if (inferred_backlink.len) { ··· 812 */ 813 strbuf_swap(&backlink, &inferred_backlink); 814 } else { 815 - fn(1, realdotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data); 816 goto done; 817 } 818 } else { 819 - fn(1, realdotgit.buf, _("unable to locate repository; .git file broken"), cb_data); 820 goto done; 821 } 822 ··· 838 * in the "copy" repository. In this case, point the "copy" worktree's 839 * .git file at the "copy" repository. 840 */ 841 - if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) { 842 strbuf_swap(&backlink, &inferred_backlink); 843 - } 844 845 strbuf_addf(&gitdir, "%s/gitdir", backlink.buf); 846 if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) 847 repair = _("gitdir unreadable"); 848 else { 849 strbuf_rtrim(&olddotgit); 850 - if (is_absolute_path(olddotgit.buf)) { 851 - strbuf_addbuf(&realolddotgit, &olddotgit); 852 - } else { 853 - strbuf_addf(&realolddotgit, "%s/%s", backlink.buf, olddotgit.buf); 854 - strbuf_realpath_forgiving(&realolddotgit, realolddotgit.buf, 0); 855 } 856 - if (fspathcmp(realolddotgit.buf, realdotgit.buf)) 857 repair = _("gitdir incorrect"); 858 } 859 860 if (repair) { 861 fn(0, gitdir.buf, repair, cb_data); 862 - write_file(gitdir.buf, "%s", relative_path(realdotgit.buf, backlink.buf, &tmp)); 863 } 864 done: 865 free(dotgit_contents); 866 strbuf_release(&olddotgit); 867 - strbuf_release(&realolddotgit); 868 strbuf_release(&backlink); 869 strbuf_release(&inferred_backlink); 870 strbuf_release(&gitdir); 871 - strbuf_release(&realdotgit); 872 strbuf_release(&dotgit); 873 - strbuf_release(&tmp); 874 } 875 876 int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire) ··· 1031 free(main_worktree_file); 1032 return res; 1033 }
··· 111 strbuf_strip_suffix(&worktree_path, "/.git"); 112 113 if (!is_absolute_path(worktree_path.buf)) { 114 + strbuf_strip_suffix(&path, "gitdir"); 115 + strbuf_addbuf(&path, &worktree_path); 116 + strbuf_realpath_forgiving(&worktree_path, path.buf, 0); 117 } 118 119 CALLOC_ARRAY(worktree, 1); ··· 376 return ret; 377 } 378 379 + void update_worktree_location(struct worktree *wt, const char *path_, 380 + int use_relative_paths) 381 { 382 struct strbuf path = STRBUF_INIT; 383 + struct strbuf dotgit = STRBUF_INIT; 384 + struct strbuf gitdir = STRBUF_INIT; 385 386 if (is_main_worktree(wt)) 387 BUG("can't relocate main worktree"); 388 389 + strbuf_realpath(&gitdir, git_common_path("worktrees/%s/gitdir", wt->id), 1); 390 strbuf_realpath(&path, path_, 1); 391 + strbuf_addf(&dotgit, "%s/.git", path.buf); 392 if (fspathcmp(wt->path, path.buf)) { 393 + write_worktree_linking_files(dotgit, gitdir, use_relative_paths); 394 395 free(wt->path); 396 wt->path = strbuf_detach(&path, NULL); 397 } 398 strbuf_release(&path); 399 + strbuf_release(&dotgit); 400 + strbuf_release(&gitdir); 401 } 402 403 int is_worktree_being_rebased(const struct worktree *wt, ··· 573 * pointing at <repo>/worktrees/<id>. 574 */ 575 static void repair_gitfile(struct worktree *wt, 576 + worktree_repair_fn fn, void *cb_data, 577 + int use_relative_paths) 578 { 579 struct strbuf dotgit = STRBUF_INIT; 580 + struct strbuf gitdir = STRBUF_INIT; 581 struct strbuf repo = STRBUF_INIT; 582 struct strbuf backlink = STRBUF_INIT; 583 char *dotgit_contents = NULL; 584 const char *repair = NULL; 585 int err; ··· 595 596 strbuf_realpath(&repo, git_common_path("worktrees/%s", wt->id), 1); 597 strbuf_addf(&dotgit, "%s/.git", wt->path); 598 + strbuf_addf(&gitdir, "%s/gitdir", repo.buf); 599 dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); 600 601 if (dotgit_contents) { ··· 613 repair = _(".git file broken"); 614 else if (fspathcmp(backlink.buf, repo.buf)) 615 repair = _(".git file incorrect"); 616 + else if (use_relative_paths == is_absolute_path(dotgit_contents)) 617 + repair = _(".git file absolute/relative path mismatch"); 618 619 if (repair) { 620 fn(0, wt->path, repair, cb_data); 621 + write_worktree_linking_files(dotgit, gitdir, use_relative_paths); 622 } 623 624 done: 625 free(dotgit_contents); 626 strbuf_release(&repo); 627 strbuf_release(&dotgit); 628 + strbuf_release(&gitdir); 629 strbuf_release(&backlink); 630 } 631 632 static void repair_noop(int iserr UNUSED, ··· 637 /* nothing */ 638 } 639 640 + void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths) 641 { 642 struct worktree **worktrees = get_worktrees_internal(1); 643 struct worktree **wt = worktrees + 1; /* +1 skips main worktree */ ··· 645 if (!fn) 646 fn = repair_noop; 647 for (; *wt; wt++) 648 + repair_gitfile(*wt, fn, cb_data, use_relative_paths); 649 free_worktrees(worktrees); 650 } 651 652 void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path) 653 { 654 struct strbuf gitdir = STRBUF_INIT; 655 struct strbuf dotgit = STRBUF_INIT; 656 + int is_relative_path; 657 658 if (is_main_worktree(wt)) 659 goto done; 660 661 + strbuf_realpath(&gitdir, git_common_path("worktrees/%s/gitdir", wt->id), 1); 662 663 + if (strbuf_read_file(&dotgit, gitdir.buf, 0) < 0) 664 goto done; 665 666 + strbuf_rtrim(&dotgit); 667 + is_relative_path = ! is_absolute_path(dotgit.buf); 668 + if (is_relative_path) { 669 + strbuf_insertf(&dotgit, 0, "%s/worktrees/%s/", old_path, wt->id); 670 strbuf_realpath_forgiving(&dotgit, dotgit.buf, 0); 671 } 672 673 if (!file_exists(dotgit.buf)) 674 goto done; 675 676 + write_worktree_linking_files(dotgit, gitdir, is_relative_path); 677 done: 678 strbuf_release(&gitdir); 679 strbuf_release(&dotgit); 680 } 681 682 void repair_worktrees_after_gitdir_move(const char *old_path) ··· 712 * won't know which <repo>/worktrees/<id>/gitdir to repair. However, we may 713 * be able to infer the gitdir by manually reading /path/to/worktree/.git, 714 * extracting the <id>, and checking if <repo>/worktrees/<id> exists. 715 + * 716 + * Returns -1 on failure and strbuf.len on success. 717 */ 718 + static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred) 719 { 720 struct strbuf actual = STRBUF_INIT; 721 const char *id; ··· 736 goto error; 737 738 strbuf_release(&actual); 739 + return inferred->len; 740 error: 741 strbuf_release(&actual); 742 strbuf_reset(inferred); /* clear invalid path */ 743 + return -1; 744 } 745 746 /* ··· 748 * the worktree's path. 749 */ 750 void repair_worktree_at_path(const char *path, 751 + worktree_repair_fn fn, void *cb_data, 752 + int use_relative_paths) 753 { 754 struct strbuf dotgit = STRBUF_INIT; 755 struct strbuf backlink = STRBUF_INIT; 756 struct strbuf inferred_backlink = STRBUF_INIT; 757 struct strbuf gitdir = STRBUF_INIT; 758 struct strbuf olddotgit = STRBUF_INIT; 759 char *dotgit_contents = NULL; 760 const char *repair = NULL; 761 int err; ··· 767 goto done; 768 769 strbuf_addf(&dotgit, "%s/.git", path); 770 + if (!strbuf_realpath(&dotgit, dotgit.buf, 0)) { 771 fn(1, path, _("not a valid path"), cb_data); 772 goto done; 773 } 774 775 + infer_backlink(dotgit.buf, &inferred_backlink); 776 strbuf_realpath_forgiving(&inferred_backlink, inferred_backlink.buf, 0); 777 + dotgit_contents = xstrdup_or_null(read_gitfile_gently(dotgit.buf, &err)); 778 if (dotgit_contents) { 779 if (is_absolute_path(dotgit_contents)) { 780 strbuf_addstr(&backlink, dotgit_contents); 781 } else { 782 + strbuf_addbuf(&backlink, &dotgit); 783 strbuf_strip_suffix(&backlink, ".git"); 784 strbuf_addstr(&backlink, dotgit_contents); 785 strbuf_realpath_forgiving(&backlink, backlink.buf, 0); 786 } 787 } else if (err == READ_GITFILE_ERR_NOT_A_FILE) { 788 + fn(1, dotgit.buf, _("unable to locate repository; .git is not a file"), cb_data); 789 goto done; 790 } else if (err == READ_GITFILE_ERR_NOT_A_REPO) { 791 if (inferred_backlink.len) { ··· 798 */ 799 strbuf_swap(&backlink, &inferred_backlink); 800 } else { 801 + fn(1, dotgit.buf, _("unable to locate repository; .git file does not reference a repository"), cb_data); 802 goto done; 803 } 804 } else { 805 + fn(1, dotgit.buf, _("unable to locate repository; .git file broken"), cb_data); 806 goto done; 807 } 808 ··· 824 * in the "copy" repository. In this case, point the "copy" worktree's 825 * .git file at the "copy" repository. 826 */ 827 + if (inferred_backlink.len && fspathcmp(backlink.buf, inferred_backlink.buf)) 828 strbuf_swap(&backlink, &inferred_backlink); 829 830 strbuf_addf(&gitdir, "%s/gitdir", backlink.buf); 831 if (strbuf_read_file(&olddotgit, gitdir.buf, 0) < 0) 832 repair = _("gitdir unreadable"); 833 + else if (use_relative_paths == is_absolute_path(olddotgit.buf)) 834 + repair = _("gitdir absolute/relative path mismatch"); 835 else { 836 strbuf_rtrim(&olddotgit); 837 + if (!is_absolute_path(olddotgit.buf)) { 838 + strbuf_insertf(&olddotgit, 0, "%s/", backlink.buf); 839 + strbuf_realpath_forgiving(&olddotgit, olddotgit.buf, 0); 840 } 841 + if (fspathcmp(olddotgit.buf, dotgit.buf)) 842 repair = _("gitdir incorrect"); 843 } 844 845 if (repair) { 846 fn(0, gitdir.buf, repair, cb_data); 847 + write_worktree_linking_files(dotgit, gitdir, use_relative_paths); 848 } 849 done: 850 free(dotgit_contents); 851 strbuf_release(&olddotgit); 852 strbuf_release(&backlink); 853 strbuf_release(&inferred_backlink); 854 strbuf_release(&gitdir); 855 strbuf_release(&dotgit); 856 } 857 858 int should_prune_worktree(const char *id, struct strbuf *reason, char **wtpath, timestamp_t expire) ··· 1013 free(main_worktree_file); 1014 return res; 1015 } 1016 + 1017 + void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, 1018 + int use_relative_paths) 1019 + { 1020 + struct strbuf path = STRBUF_INIT; 1021 + struct strbuf repo = STRBUF_INIT; 1022 + struct strbuf tmp = STRBUF_INIT; 1023 + 1024 + strbuf_addbuf(&path, &dotgit); 1025 + strbuf_strip_suffix(&path, "/.git"); 1026 + strbuf_realpath(&path, path.buf, 1); 1027 + strbuf_addbuf(&repo, &gitdir); 1028 + strbuf_strip_suffix(&repo, "/gitdir"); 1029 + strbuf_realpath(&repo, repo.buf, 1); 1030 + 1031 + if (use_relative_paths && !the_repository->repository_format_relative_worktrees) { 1032 + if (upgrade_repository_format(1) < 0) 1033 + die(_("unable to upgrade repository format to support relative worktrees")); 1034 + if (git_config_set_gently("extensions.relativeWorktrees", "true")) 1035 + die(_("unable to set extensions.relativeWorktrees setting")); 1036 + the_repository->repository_format_relative_worktrees = 1; 1037 + } 1038 + 1039 + if (use_relative_paths) { 1040 + write_file(gitdir.buf, "%s/.git", relative_path(path.buf, repo.buf, &tmp)); 1041 + write_file(dotgit.buf, "gitdir: %s", relative_path(repo.buf, path.buf, &tmp)); 1042 + } else { 1043 + write_file(gitdir.buf, "%s/.git", path.buf); 1044 + write_file(dotgit.buf, "gitdir: %s", repo.buf); 1045 + } 1046 + 1047 + strbuf_release(&path); 1048 + strbuf_release(&repo); 1049 + strbuf_release(&tmp); 1050 + }
+18 -4
worktree.h
··· 117 /* 118 * Update worktrees/xxx/gitdir with the new path. 119 */ 120 - void update_worktree_location(struct worktree *wt, 121 - const char *path_); 122 123 typedef void (* worktree_repair_fn)(int iserr, const char *path, 124 const char *msg, void *cb_data); ··· 129 * function, if non-NULL, is called with the path of the worktree and a 130 * description of the repair or error, along with the callback user-data. 131 */ 132 - void repair_worktrees(worktree_repair_fn, void *cb_data); 133 134 /* 135 * Repair the linked worktrees after the gitdir has been moved. ··· 151 * worktree and a description of the repair or error, along with the callback 152 * user-data. 153 */ 154 - void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data); 155 156 /* 157 * Free up the memory for a worktree. ··· 214 * if any of these steps fail. 215 */ 216 int init_worktree_config(struct repository *r); 217 218 #endif
··· 117 /* 118 * Update worktrees/xxx/gitdir with the new path. 119 */ 120 + void update_worktree_location(struct worktree *wt, const char *path_, 121 + int use_relative_paths); 122 123 typedef void (* worktree_repair_fn)(int iserr, const char *path, 124 const char *msg, void *cb_data); ··· 129 * function, if non-NULL, is called with the path of the worktree and a 130 * description of the repair or error, along with the callback user-data. 131 */ 132 + void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths); 133 134 /* 135 * Repair the linked worktrees after the gitdir has been moved. ··· 151 * worktree and a description of the repair or error, along with the callback 152 * user-data. 153 */ 154 + void repair_worktree_at_path(const char *, worktree_repair_fn, 155 + void *cb_data, int use_relative_paths); 156 157 /* 158 * Free up the memory for a worktree. ··· 215 * if any of these steps fail. 216 */ 217 int init_worktree_config(struct repository *r); 218 + 219 + /** 220 + * Write the .git file and gitdir file that links the worktree to the repository. 221 + * 222 + * The `dotgit` parameter is the path to the worktree's .git file, and `gitdir` 223 + * is the path to the repository's `gitdir` file. 224 + * 225 + * Example 226 + * dotgit: "/path/to/foo/.git" 227 + * gitdir: "/path/to/repo/worktrees/foo/gitdir" 228 + */ 229 + void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir, 230 + int use_relative_paths); 231 232 #endif