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 versionmaint
commit
3b11c9139d
|
@ -63,6 +63,12 @@ Note that this setting should only be set by linkgit:git-init[1] or
|
|||
linkgit:git-clone[1]. Trying to change it after initialization will not
|
||||
work and will produce hard-to-diagnose issues.
|
||||
|
||||
relativeWorktrees::
|
||||
If enabled, indicates at least one worktree has been linked with
|
||||
relative paths. Automatically set if a worktree has been created or
|
||||
repaired with either the `--relative-paths` option or with the
|
||||
`worktree.useRelativePaths` config set to `true`.
|
||||
|
||||
worktreeConfig::
|
||||
If enabled, then worktrees will load config settings from the
|
||||
`$GIT_DIR/config.worktree` file in addition to the
|
||||
|
|
|
@ -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,14 @@ 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::
|
||||
Link worktrees using relative paths or absolute paths (default).
|
||||
Overrides the `worktree.useRelativePaths` config option, see
|
||||
linkgit:git-config[1].
|
||||
+
|
||||
With `repair`, the linking files will be updated if there's an absolute/relative
|
||||
mismatch, even if the links are correct.
|
||||
|
||||
--[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);
|
||||
|
@ -415,8 +420,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;
|
||||
|
@ -492,10 +496,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, "../..");
|
||||
|
@ -579,12 +580,9 @@ done:
|
|||
|
||||
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;
|
||||
|
@ -798,12 +796,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");
|
||||
|
@ -1195,6 +1196,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;
|
||||
|
@ -1247,7 +1250,7 @@ static int move_worktree(int ac, const char **av, const char *prefix,
|
|||
if (rename(wt->path, dst.buf) == -1)
|
||||
die_errno(_("failed to move '%s' to '%s'"), wt->path, dst.buf);
|
||||
|
||||
update_worktree_location(wt, dst.buf);
|
||||
update_worktree_location(wt, dst.buf, use_relative_paths);
|
||||
|
||||
strbuf_release(&dst);
|
||||
free_worktrees(worktrees);
|
||||
|
@ -1390,6 +1393,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;
|
||||
|
@ -1397,8 +1402,8 @@ static int repair(int ac, const char **av, const char *prefix,
|
|||
ac = parse_options(ac, av, prefix, options, git_worktree_repair_usage, 0);
|
||||
p = ac > 0 ? av : self;
|
||||
for (; *p; p++)
|
||||
repair_worktree_at_path(*p, report_repair, &rc);
|
||||
repair_worktrees(report_repair, &rc);
|
||||
repair_worktree_at_path(*p, report_repair, &rc, use_relative_paths);
|
||||
repair_worktrees(report_repair, &rc, use_relative_paths);
|
||||
return rc;
|
||||
}
|
||||
|
||||
|
|
|
@ -283,6 +283,7 @@ int repo_init(struct repository *repo,
|
|||
repo_set_compat_hash_algo(repo, format.compat_hash_algo);
|
||||
repo_set_ref_storage_format(repo, format.ref_storage_format);
|
||||
repo->repository_format_worktree_config = format.worktree_config;
|
||||
repo->repository_format_relative_worktrees = format.relative_worktrees;
|
||||
|
||||
/* take ownership of format.partial_clone */
|
||||
repo->repository_format_partial_clone = format.partial_clone;
|
||||
|
|
|
@ -150,6 +150,7 @@ struct repository {
|
|||
|
||||
/* Configurations */
|
||||
int repository_format_worktree_config;
|
||||
int repository_format_relative_worktrees;
|
||||
|
||||
/* Indicate if a repository has a different 'commondir' from 'gitdir' */
|
||||
unsigned different_commondir:1;
|
||||
|
|
39
setup.c
39
setup.c
|
@ -683,6 +683,9 @@ static enum extension_result handle_extension(const char *var,
|
|||
"extensions.refstorage", value);
|
||||
data->ref_storage_format = format;
|
||||
return EXTENSION_OK;
|
||||
} else if (!strcmp(ext, "relativeworktrees")) {
|
||||
data->relative_worktrees = git_config_bool(var, value);
|
||||
return EXTENSION_OK;
|
||||
}
|
||||
return EXTENSION_UNKNOWN;
|
||||
}
|
||||
|
@ -1854,6 +1857,8 @@ const char *setup_git_directory_gently(int *nongit_ok)
|
|||
repo_fmt.ref_storage_format);
|
||||
the_repository->repository_format_worktree_config =
|
||||
repo_fmt.worktree_config;
|
||||
the_repository->repository_format_relative_worktrees =
|
||||
repo_fmt.relative_worktrees;
|
||||
/* take ownership of repo_fmt.partial_clone */
|
||||
the_repository->repository_format_partial_clone =
|
||||
repo_fmt.partial_clone;
|
||||
|
@ -1950,6 +1955,8 @@ void check_repository_format(struct repository_format *fmt)
|
|||
fmt->ref_storage_format);
|
||||
the_repository->repository_format_worktree_config =
|
||||
fmt->worktree_config;
|
||||
the_repository->repository_format_relative_worktrees =
|
||||
fmt->relative_worktrees;
|
||||
the_repository->repository_format_partial_clone =
|
||||
xstrdup_or_null(fmt->partial_clone);
|
||||
clear_repository_format(&repo_fmt);
|
||||
|
@ -2204,8 +2211,8 @@ void initialize_repository_version(int hash_algo,
|
|||
enum ref_storage_format ref_storage_format,
|
||||
int reinit)
|
||||
{
|
||||
char repo_version_string[10];
|
||||
int repo_version = GIT_REPO_VERSION;
|
||||
struct strbuf repo_version = STRBUF_INIT;
|
||||
int target_version = GIT_REPO_VERSION;
|
||||
|
||||
/*
|
||||
* Note that we initialize the repository version to 1 when the ref
|
||||
|
@ -2216,12 +2223,7 @@ void initialize_repository_version(int hash_algo,
|
|||
*/
|
||||
if (hash_algo != GIT_HASH_SHA1 ||
|
||||
ref_storage_format != REF_STORAGE_FORMAT_FILES)
|
||||
repo_version = GIT_REPO_VERSION_READ;
|
||||
|
||||
/* This forces creation of new config file */
|
||||
xsnprintf(repo_version_string, sizeof(repo_version_string),
|
||||
"%d", repo_version);
|
||||
git_config_set("core.repositoryformatversion", repo_version_string);
|
||||
target_version = GIT_REPO_VERSION_READ;
|
||||
|
||||
if (hash_algo != GIT_HASH_SHA1 && hash_algo != GIT_HASH_UNKNOWN)
|
||||
git_config_set("extensions.objectformat",
|
||||
|
@ -2234,6 +2236,25 @@ void initialize_repository_version(int hash_algo,
|
|||
ref_storage_format_to_name(ref_storage_format));
|
||||
else if (reinit)
|
||||
git_config_set_gently("extensions.refstorage", NULL);
|
||||
|
||||
if (reinit) {
|
||||
struct strbuf config = STRBUF_INIT;
|
||||
struct repository_format repo_fmt = REPOSITORY_FORMAT_INIT;
|
||||
|
||||
strbuf_git_common_path(&config, the_repository, "config");
|
||||
read_repository_format(&repo_fmt, config.buf);
|
||||
|
||||
if (repo_fmt.v1_only_extensions.nr)
|
||||
target_version = GIT_REPO_VERSION_READ;
|
||||
|
||||
strbuf_release(&config);
|
||||
clear_repository_format(&repo_fmt);
|
||||
}
|
||||
|
||||
strbuf_addf(&repo_version, "%d", target_version);
|
||||
git_config_set("core.repositoryformatversion", repo_version.buf);
|
||||
|
||||
strbuf_release(&repo_version);
|
||||
}
|
||||
|
||||
static int is_reinit(void)
|
||||
|
@ -2333,7 +2354,7 @@ static int create_default_files(const char *template_path,
|
|||
adjust_shared_perm(repo_get_git_dir(the_repository));
|
||||
}
|
||||
|
||||
initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, 0);
|
||||
initialize_repository_version(fmt->hash_algo, fmt->ref_storage_format, reinit);
|
||||
|
||||
/* Check filemode trustability */
|
||||
path = git_path_buf(&buf, "config");
|
||||
|
|
1
setup.h
1
setup.h
|
@ -129,6 +129,7 @@ struct repository_format {
|
|||
int precious_objects;
|
||||
char *partial_clone; /* value of extensions.partialclone */
|
||||
int worktree_config;
|
||||
int relative_worktrees;
|
||||
int is_bare;
|
||||
int hash_algo;
|
||||
int compat_hash_algo;
|
||||
|
|
|
@ -433,6 +433,12 @@ test_expect_success SYMLINKS 're-init to move gitdir symlink' '
|
|||
sep_git_dir_worktree () {
|
||||
test_when_finished "rm -rf mainwt linkwt seprepo" &&
|
||||
git init mainwt &&
|
||||
if test "relative" = $2
|
||||
then
|
||||
test_config -C mainwt worktree.useRelativePaths true
|
||||
else
|
||||
test_config -C mainwt worktree.useRelativePaths false
|
||||
fi
|
||||
test_commit -C mainwt gumby &&
|
||||
git -C mainwt worktree add --detach ../linkwt &&
|
||||
git -C "$1" init --separate-git-dir ../seprepo &&
|
||||
|
@ -441,12 +447,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' '
|
||||
|
|
|
@ -1206,4 +1206,50 @@ 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" &&
|
||||
test_config worktree.useRelativePaths false &&
|
||||
git worktree add --relative-paths ./relative &&
|
||||
echo "gitdir: ../.git/worktrees/relative" >expect &&
|
||||
test_cmp expect relative/.git &&
|
||||
echo "../../../relative/.git" >expect &&
|
||||
test_cmp expect .git/worktrees/relative/gitdir
|
||||
'
|
||||
|
||||
test_expect_success 'can create worktrees with absolute paths' '
|
||||
test_config worktree.useRelativePaths true &&
|
||||
git worktree add ./relative &&
|
||||
echo "gitdir: ../.git/worktrees/relative" >expect &&
|
||||
test_cmp expect relative/.git &&
|
||||
git worktree add --no-relative-paths ./absolute &&
|
||||
echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
|
||||
test_cmp expect absolute/.git &&
|
||||
echo "$(pwd)/absolute/.git" >expect &&
|
||||
test_cmp expect .git/worktrees/absolute/gitdir
|
||||
'
|
||||
|
||||
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 --relative-paths wt1 &&
|
||||
cd .. &&
|
||||
mv repo moved &&
|
||||
cd moved/wt1 &&
|
||||
git worktree list >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 &&
|
||||
test_cmp_config -C repo 1 core.repositoryformatversion &&
|
||||
test_cmp_config -C repo true extensions.relativeworktrees
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -119,11 +119,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 &&
|
||||
|
|
|
@ -260,6 +260,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 &&
|
||||
(
|
||||
|
@ -279,6 +280,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 &&
|
||||
|
|
|
@ -246,4 +246,29 @@ test_expect_success 'not remove a repo with initialized submodule' '
|
|||
)
|
||||
'
|
||||
|
||||
test_expect_success 'move worktree with absolute path to relative path' '
|
||||
test_config worktree.useRelativePaths false &&
|
||||
git worktree add ./absolute &&
|
||||
git worktree move --relative-paths absolute relative &&
|
||||
echo "gitdir: ../.git/worktrees/absolute" >expect &&
|
||||
test_cmp expect relative/.git &&
|
||||
echo "../../../relative/.git" >expect &&
|
||||
test_cmp expect .git/worktrees/absolute/gitdir &&
|
||||
test_config worktree.useRelativePaths true &&
|
||||
git worktree move relative relative2 &&
|
||||
echo "gitdir: ../.git/worktrees/absolute" >expect &&
|
||||
test_cmp expect relative2/.git &&
|
||||
echo "../../../relative2/.git" >expect &&
|
||||
test_cmp expect .git/worktrees/absolute/gitdir
|
||||
'
|
||||
|
||||
test_expect_success 'move worktree with relative path to absolute path' '
|
||||
test_config worktree.useRelativePaths true &&
|
||||
git worktree move --no-relative-paths relative2 absolute &&
|
||||
echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
|
||||
test_cmp expect absolute/.git &&
|
||||
echo "$(pwd)/absolute/.git" >expect &&
|
||||
test_cmp expect .git/worktrees/absolute/gitdir
|
||||
'
|
||||
|
||||
test_done
|
||||
|
|
|
@ -215,4 +215,43 @@ test_expect_success 'repair copied main and linked worktrees' '
|
|||
test_cmp dup/linked.expect dup/linked/.git
|
||||
'
|
||||
|
||||
test_expect_success 'repair worktree with relative path with missing gitfile' '
|
||||
test_when_finished "rm -rf main wt" &&
|
||||
test_create_repo main &&
|
||||
git -C main config worktree.useRelativePaths true &&
|
||||
test_commit -C main init &&
|
||||
git -C main worktree add --detach ../wt &&
|
||||
rm wt/.git &&
|
||||
test_path_is_missing wt/.git &&
|
||||
git -C main worktree repair &&
|
||||
echo "gitdir: ../main/.git/worktrees/wt" >expect &&
|
||||
test_cmp expect wt/.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
|
||||
|
|
|
@ -1,38 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
test_description='test worktrees linked with relative paths'
|
||||
|
||||
. ./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
|
|
@ -170,7 +170,7 @@ test_expect_success 'fsck with invalid or bogus skipList input' '
|
|||
test_must_fail git -c fsck.skipList=does-not-exist -c fsck.missingEmail=ignore fsck 2>err &&
|
||||
test_grep "could not open.*: does-not-exist" err &&
|
||||
test_must_fail git -c fsck.skipList=.git/config -c fsck.missingEmail=ignore fsck 2>err &&
|
||||
test_grep "invalid object name: \[core\]" err
|
||||
test_grep "invalid object name: " err
|
||||
'
|
||||
|
||||
test_expect_success 'fsck with other accepted skipList input (comments & empty lines)' '
|
||||
|
@ -233,7 +233,7 @@ test_expect_success 'push with receive.fsck.skipList' '
|
|||
test_grep "could not open.*: does-not-exist" err &&
|
||||
git --git-dir=dst/.git config receive.fsck.skipList config &&
|
||||
test_must_fail git push --porcelain dst bogus 2>err &&
|
||||
test_grep "invalid object name: \[core\]" err &&
|
||||
test_grep "invalid object name: " err &&
|
||||
|
||||
git --git-dir=dst/.git config receive.fsck.skipList SKIP &&
|
||||
git push --porcelain dst bogus
|
||||
|
@ -262,7 +262,7 @@ test_expect_success 'fetch with fetch.fsck.skipList' '
|
|||
test_grep "could not open.*: does-not-exist" err &&
|
||||
git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/config &&
|
||||
test_must_fail git --git-dir=dst/.git fetch "file://$(pwd)" $refspec 2>err &&
|
||||
test_grep "invalid object name: \[core\]" err &&
|
||||
test_grep "invalid object name: " err &&
|
||||
|
||||
git --git-dir=dst/.git config fetch.fsck.skipList dst/.git/SKIP &&
|
||||
git --git-dir=dst/.git fetch "file://$(pwd)" $refspec
|
||||
|
|
157
worktree.c
157
worktree.c
|
@ -111,9 +111,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);
|
||||
|
@ -376,32 +376,28 @@ done:
|
|||
return ret;
|
||||
}
|
||||
|
||||
void update_worktree_location(struct worktree *wt, const char *path_)
|
||||
void update_worktree_location(struct worktree *wt, const char *path_,
|
||||
int use_relative_paths)
|
||||
{
|
||||
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, use_relative_paths);
|
||||
|
||||
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,
|
||||
|
@ -577,12 +573,13 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
|
|||
* pointing at <repo>/worktrees/<id>.
|
||||
*/
|
||||
static void repair_gitfile(struct worktree *wt,
|
||||
worktree_repair_fn fn, void *cb_data)
|
||||
worktree_repair_fn fn, void *cb_data,
|
||||
int use_relative_paths)
|
||||
{
|
||||
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, use_relative_paths);
|
||||
}
|
||||
|
||||
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,
|
||||
|
@ -637,7 +637,7 @@ static void repair_noop(int iserr UNUSED,
|
|||
/* nothing */
|
||||
}
|
||||
|
||||
void repair_worktrees(worktree_repair_fn fn, void *cb_data)
|
||||
void repair_worktrees(worktree_repair_fn fn, void *cb_data, int use_relative_paths)
|
||||
{
|
||||
struct worktree **worktrees = get_worktrees_internal(1);
|
||||
struct worktree **wt = worktrees + 1; /* +1 skips main worktree */
|
||||
|
@ -645,51 +645,38 @@ void repair_worktrees(worktree_repair_fn fn, void *cb_data)
|
|||
if (!fn)
|
||||
fn = repair_noop;
|
||||
for (; *wt; wt++)
|
||||
repair_gitfile(*wt, fn, cb_data);
|
||||
repair_gitfile(*wt, fn, cb_data, use_relative_paths);
|
||||
free_worktrees(worktrees);
|
||||
}
|
||||
|
||||
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;
|
||||
int is_relative_path;
|
||||
|
||||
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);
|
||||
is_relative_path = ! is_absolute_path(dotgit.buf);
|
||||
if (is_relative_path) {
|
||||
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, is_relative_path);
|
||||
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,8 +712,10 @@ 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)
|
||||
static ssize_t infer_backlink(const char *gitfile, struct strbuf *inferred)
|
||||
{
|
||||
struct strbuf actual = STRBUF_INIT;
|
||||
const char *id;
|
||||
|
@ -747,12 +736,11 @@ static int infer_backlink(const char *gitfile, struct strbuf *inferred)
|
|||
goto error;
|
||||
|
||||
strbuf_release(&actual);
|
||||
return 1;
|
||||
|
||||
return inferred->len;
|
||||
error:
|
||||
strbuf_release(&actual);
|
||||
strbuf_reset(inferred); /* clear invalid path */
|
||||
return 0;
|
||||
return -1;
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -760,16 +748,14 @@ error:
|
|||
* the worktree's path.
|
||||
*/
|
||||
void repair_worktree_at_path(const char *path,
|
||||
worktree_repair_fn fn, void *cb_data)
|
||||
worktree_repair_fn fn, void *cb_data,
|
||||
int use_relative_paths)
|
||||
{
|
||||
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 +767,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 +798,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 +824,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, use_relative_paths);
|
||||
}
|
||||
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 +1013,38 @@ cleanup:
|
|||
free(main_worktree_file);
|
||||
return res;
|
||||
}
|
||||
|
||||
void write_worktree_linking_files(struct strbuf dotgit, struct strbuf gitdir,
|
||||
int use_relative_paths)
|
||||
{
|
||||
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 && !the_repository->repository_format_relative_worktrees) {
|
||||
if (upgrade_repository_format(1) < 0)
|
||||
die(_("unable to upgrade repository format to support relative worktrees"));
|
||||
if (git_config_set_gently("extensions.relativeWorktrees", "true"))
|
||||
die(_("unable to set extensions.relativeWorktrees setting"));
|
||||
the_repository->repository_format_relative_worktrees = 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);
|
||||
}
|
||||
|
|
22
worktree.h
22
worktree.h
|
@ -117,8 +117,8 @@ int validate_worktree(const struct worktree *wt,
|
|||
/*
|
||||
* Update worktrees/xxx/gitdir with the new path.
|
||||
*/
|
||||
void update_worktree_location(struct worktree *wt,
|
||||
const char *path_);
|
||||
void update_worktree_location(struct worktree *wt, const char *path_,
|
||||
int use_relative_paths);
|
||||
|
||||
typedef void (* worktree_repair_fn)(int iserr, const char *path,
|
||||
const char *msg, void *cb_data);
|
||||
|
@ -129,7 +129,7 @@ typedef void (* worktree_repair_fn)(int iserr, const char *path,
|
|||
* function, if non-NULL, is called with the path of the worktree and a
|
||||
* description of the repair or error, along with the callback user-data.
|
||||
*/
|
||||
void repair_worktrees(worktree_repair_fn, void *cb_data);
|
||||
void repair_worktrees(worktree_repair_fn, void *cb_data, int use_relative_paths);
|
||||
|
||||
/*
|
||||
* Repair the linked worktrees after the gitdir has been moved.
|
||||
|
@ -151,7 +151,8 @@ void repair_worktree_after_gitdir_move(struct worktree *wt, const char *old_path
|
|||
* worktree and a description of the repair or error, along with the callback
|
||||
* user-data.
|
||||
*/
|
||||
void repair_worktree_at_path(const char *, worktree_repair_fn, void *cb_data);
|
||||
void repair_worktree_at_path(const char *, worktree_repair_fn,
|
||||
void *cb_data, int use_relative_paths);
|
||||
|
||||
/*
|
||||
* Free up the memory for a worktree.
|
||||
|
@ -215,4 +216,17 @@ 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,
|
||||
int use_relative_paths);
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in New Issue