Skip to content

Commit

Permalink
Merge branch 'cw/worktree-extension' into seen
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
ttaylorr committed Nov 1, 2024
2 parents dd69188 + 298327f commit 3a2c1aa
Show file tree
Hide file tree
Showing 18 changed files with 331 additions and 142 deletions.
6 changes: 6 additions & 0 deletions Documentation/config/extensions.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
10 changes: 10 additions & 0 deletions Documentation/config/worktree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -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.
7 changes: 7 additions & 0 deletions Documentation/git-worktree.txt
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,13 @@ To remove a locked worktree, specify `--force` twice.
This can also be set up as the default behaviour by using the
`worktree.guessRemote` config option.

--[no-]relative-paths::
Overrides the `worktree.useRelativePaths` config option, see
linkgit:git-config[1].
+
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
Expand Down
29 changes: 17 additions & 12 deletions builtin/worktree.c
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -414,8 +419,7 @@ static int add_worktree(const char *path, const char *refname,
const struct add_opts *opts)
{
struct strbuf sb_git = STRBUF_INIT, sb_repo = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT, sb_tmp = STRBUF_INIT;
struct strbuf sb_path_realpath = STRBUF_INIT, sb_repo_realpath = STRBUF_INIT;
struct strbuf sb = STRBUF_INIT;
const char *name;
struct strvec child_env = STRVEC_INIT;
unsigned int counter = 0;
Expand Down Expand Up @@ -491,10 +495,7 @@ static int add_worktree(const char *path, const char *refname,

strbuf_reset(&sb);
strbuf_addf(&sb, "%s/gitdir", sb_repo.buf);
strbuf_realpath(&sb_path_realpath, path, 1);
strbuf_realpath(&sb_repo_realpath, sb_repo.buf, 1);
write_file(sb.buf, "%s/.git", relative_path(sb_path_realpath.buf, sb_repo_realpath.buf, &sb_tmp));
write_file(sb_git.buf, "gitdir: %s", relative_path(sb_repo_realpath.buf, sb_path_realpath.buf, &sb_tmp));
write_worktree_linking_files(sb_git, sb, opts->relative_paths);
strbuf_reset(&sb);
strbuf_addf(&sb, "%s/commondir", sb_repo.buf);
write_file(sb.buf, "../..");
Expand Down Expand Up @@ -578,12 +579,9 @@ static int add_worktree(const char *path, const char *refname,

strvec_clear(&child_env);
strbuf_release(&sb);
strbuf_release(&sb_tmp);
strbuf_release(&symref);
strbuf_release(&sb_repo);
strbuf_release(&sb_repo_realpath);
strbuf_release(&sb_git);
strbuf_release(&sb_path_realpath);
strbuf_release(&sb_name);
free_worktree(wt);
return ret;
Expand Down Expand Up @@ -796,12 +794,15 @@ static int add(int ac, const char **av, const char *prefix)
PARSE_OPT_NOARG | PARSE_OPT_OPTARG),
OPT_BOOL(0, "guess-remote", &guess_remote,
N_("try to match the new branch name with a remote-tracking branch")),
OPT_BOOL(0, "relative-paths", &opts.relative_paths,
N_("use relative paths for worktrees")),
OPT_END()
};
int ret;

memset(&opts, 0, sizeof(opts));
opts.checkout = 1;
opts.relative_paths = use_relative_paths;
ac = parse_options(ac, av, prefix, options, git_worktree_add_usage, 0);
if (!!opts.detach + !!new_branch + !!new_branch_force > 1)
die(_("options '%s', '%s', and '%s' cannot be used together"), "-b", "-B", "--detach");
Expand Down Expand Up @@ -1189,6 +1190,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;
Expand Down Expand Up @@ -1241,7 +1244,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);
Expand Down Expand Up @@ -1382,15 +1385,17 @@ 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;

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;
}

Expand Down
1 change: 1 addition & 0 deletions repository.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions repository.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
39 changes: 30 additions & 9 deletions setup.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand All @@ -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",
Expand All @@ -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)
Expand Down Expand Up @@ -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");
Expand Down
1 change: 1 addition & 0 deletions setup.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
22 changes: 18 additions & 4 deletions t/t0001-init.sh
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,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
git -C mainwt config worktree.useRelativePaths true
else
git -C mainwt config worktree.useRelativePaths false
fi
test_commit -C mainwt gumby &&
git -C mainwt worktree add --detach ../linkwt &&
git -C "$1" init --separate-git-dir ../seprepo &&
Expand All @@ -442,12 +448,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 (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' '
sep_git_dir_worktree linkwt
test_expect_success 're-init to move gitdir within linked worktree (relative)' '
sep_git_dir_worktree linkwt relative
'

test_expect_success MINGW '.git hidden' '
Expand Down
54 changes: 54 additions & 0 deletions t/t2400-worktree-add.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1207,4 +1207,58 @@ test_expect_success '"add" with initialized submodule, with submodule.recurse se
git -C project-clone -c submodule.recurse worktree add ../project-5
'

test_expect_success 'can create worktrees with relative paths' '
test_when_finished "git worktree remove relative" &&
git config worktree.useRelativePaths false &&
git worktree add --relative-paths ./relative &&
cat relative/.git >actual &&
echo "gitdir: ../.git/worktrees/relative" >expect &&
test_cmp expect actual &&
cat .git/worktrees/relative/gitdir >actual &&
echo "../../../relative/.git" >expect &&
test_cmp expect actual
'

test_expect_success 'can create worktrees with absolute paths' '
git config worktree.useRelativePaths true &&
git worktree add ./relative &&
cat relative/.git >actual &&
echo "gitdir: ../.git/worktrees/relative" >expect &&
test_cmp expect actual &&
git worktree add --no-relative-paths ./absolute &&
cat absolute/.git >actual &&
echo "gitdir: $(pwd)/.git/worktrees/absolute" >expect &&
test_cmp expect actual
'

test_expect_success 'move repo without breaking relative internal links' '
test_when_finished rm -rf repo moved &&
git init repo &&
(
cd repo &&
git config worktree.useRelativePaths true &&
test_commit initial &&
git worktree add wt1 &&
cd .. &&
mv repo moved &&
cd moved/wt1 &&
git status >out 2>err &&
test_must_be_empty err
)
'

test_expect_success 'relative worktree sets extension config' '
test_when_finished "rm -rf repo" &&
git init repo &&
git -C repo commit --allow-empty -m base &&
git -C repo worktree add --relative-paths ./foo &&
git -C repo config get core.repositoryformatversion >actual &&
echo 1 >expected &&
test_cmp expected actual &&
git -C repo config get extensions.relativeworktrees >actual &&
echo true >expected &&
test_cmp expected actual
'

test_done
3 changes: 2 additions & 1 deletion t/t2401-worktree-prune.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,11 +120,12 @@ test_expect_success 'prune duplicate (main/linked)' '
! test -d .git/worktrees/wt
'

test_expect_success 'not prune proper worktrees when run inside linked worktree' '
test_expect_success 'not prune proper worktrees inside linked worktree with relative paths' '
test_when_finished rm -rf repo wt_ext &&
git init repo &&
(
cd repo &&
git config worktree.useRelativePaths true &&
echo content >file &&
git add file &&
git commit -m msg &&
Expand Down
Loading

0 comments on commit 3a2c1aa

Please sign in to comment.