diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 9e862fbcf79..0f0642ac039 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -10,6 +10,7 @@ SYNOPSIS
--------
[verse]
'git worktree add' [-f] [--detach] [--checkout] [--lock [--reason <string>]] [-b <new-branch>] <path> [<commit-ish>]
+'git worktree init-worktree-config'
'git worktree list' [-v | --porcelain]
'git worktree lock' [--reason <string>] <worktree>
'git worktree move' <worktree> <new-path>
@@ -92,6 +93,14 @@ checked out in the new working tree, if it's not checked out anywhere
else, otherwise the command will refuse to create the working tree (unless
`--force` is used).
+init-worktree-config::
+
+Initialize config settings to enable worktree-specific config settings.
+This will set `core.repositoryFormatversion=1` and enable
+`extensions.worktreeConfig`, which might cause some third-party tools from
+being able to operate on your repository. See CONFIGURATION FILE for more
+details.
+
list::
List details of each working tree. The main working tree is listed first,
@@ -290,10 +299,10 @@ already present in the config file, they will be applied to the main
working trees only.
In order to have configuration specific to working trees, you can turn
-on the `worktreeConfig` extension, e.g.:
+on the `worktreeConfig` extension, using this command:
------------
-$ git config extensions.worktreeConfig true
+$ git worktree init-worktree-config
------------
In this mode, specific configuration stays in the path pointed by `git
@@ -303,9 +312,12 @@ versions will refuse to access repositories with this extension.
Note that in this file, the exception for `core.bare` and `core.worktree`
is gone. If they exist in `$GIT_DIR/config`, you must move
-them to the `config.worktree` of the main working tree. You may also
-take this opportunity to review and move other configuration that you
-do not want to share to all working trees:
+them to the `config.worktree` of the main working tree. These keys are
+moved automatically when you use the `git worktree init-worktree-config`
+command.
+
+You may also take this opportunity to review and move other configuration
+that you do not want to share to all working trees:
- `core.worktree` and `core.bare` should never be shared
diff --git a/builtin/worktree.c b/builtin/worktree.c
index a57fcd0f3c5..937ee6fc38b 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -17,6 +17,7 @@
static const char * const worktree_usage[] = {
N_("git worktree add [<options>] <path> [<commit-ish>]"),
+ N_("git worktree init-worktree-config"),
N_("git worktree list [<options>]"),
N_("git worktree lock [<options>] <path>"),
N_("git worktree move <worktree> <new-path>"),@@ -1031,6 +1032,85 @@ static int repair(int ac, const char **av, const char *prefix)
return rc;
}
+static int move_config_setting(const char *key, const char *value,
+ const char *from_file, const char *to_file)
+{
+ if (git_config_set_in_file_gently(to_file, key, value))
+ return error(_("unable to set %s in '%s'"), key, to_file);
+ if (git_config_set_in_file_gently(from_file, key, NULL))
+ return error(_("unable to unset %s in '%s'"), key, from_file);
+ return 0;
+}
+
+static int init_worktree_config(int ac, const char **av, const char *prefix)
+{
+ struct repository *r = the_repository;
+ struct option options[] = {
+ OPT_END()
+ };
+ int res = 0;
+ int bare = 0;
+ struct config_set cs = { 0 };
+ const char *core_worktree;
+ char *common_config_file = xstrfmt("%s/config", r->commondir);
+ char *main_worktree_file = xstrfmt("%s/config.worktree", r->commondir);
+
+ /* Report error on any arguments */
+ ac = parse_options(ac, av, prefix, options, worktree_usage, 0);
+ if (ac)
+ usage_with_options(worktree_usage, options);
+
+ git_configset_init(&cs);
+ git_configset_add_file(&cs, common_config_file);
+
+ /*
+ * If the format and extension are already enabled, then we can
+ * skip the upgrade process.
+ */
+ if (repository_format_worktree_config)
+ return 0;
+
+ if (upgrade_repository_format(r, 1) < 0) {
+ res = error(_("unable to upgrade repository format to enable worktreeConfig"));
+ goto cleanup;
+ }
+ if ((res = git_config_set_gently("extensions.worktreeConfig", "true"))) {
+ error(_("failed to set extensions.worktreeConfig setting"));
+ goto cleanup;
+ }
+
+ /*
+ * If core.bare is true in the common config file, then we need to
+ * move it to the base worktree's config file or it will break all
+ * worktrees. If it is false, then leave it in place because it
+ * _could_ be negating a global core.bare=true.
+ */
+ if (!git_configset_get_bool(&cs, "core.bare", &bare) && bare) {
+ if ((res = move_config_setting("core.bare", "true",
+ common_config_file,
+ main_worktree_file)))
+ goto cleanup;
+ }
+ /*
+ * If core.worktree is set, then the base worktree is located
+ * somewhere different than the parent of the common Git dir.
+ * Relocate that value to avoid breaking all worktrees with this
+ * upgrade to worktree config.
+ */
+ if (!git_configset_get_string_tmp(&cs, "core.worktree", &core_worktree)) {
+ if ((res = move_config_setting("core.worktree", core_worktree,
+ common_config_file,
+ main_worktree_file)))
+ goto cleanup;
+ }
+
+cleanup:
+ git_configset_clear(&cs);
+ free(common_config_file);
+ free(main_worktree_file);
+ return res;
+}
+
int cmd_worktree(int ac, const char **av, const char *prefix)
{
struct option options[] = {@@ -1045,6 +1125,8 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
prefix = "";
if (!strcmp(av[1], "add"))
return add(ac - 1, av + 1, prefix);
+ if (!strcmp(av[1], "init-worktree-config"))
+ return init_worktree_config(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "prune"))
return prune(ac - 1, av + 1, prefix);
if (!strcmp(av[1], "list"))
diff --git a/t/t2407-worktree-init-worktree-config.sh b/t/t2407-worktree-init-worktree-config.sh
new file mode 100755
index 00000000000..b3bd0fa1322
--- /dev/null
+++ b/t/t2407-worktree-init-worktree-config.sh
@@ -0,0 +1,68 @@
+#!/bin/sh
+
+test_description='test git worktree init-worktree-config'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ git init base &&
+ test_commit -C base commit &&
+ git -C base worktree add --detach worktree
+'
+
+reset_config_when_finished () {
+ test_when_finished git -C base config --unset core.repositoryFormatVersion &&
+ test_when_finished git -C base config --unset extensions.worktreeConfig &&
+ rm -rf base/.git/config.worktree &&
+ rm -rf base/.git/worktrees/worktree/config.worktree
+}
+
+test_expect_success 'upgrades repo format and adds extension' '
+ reset_config_when_finished &&
+ git -C base worktree init-worktree-config >out 2>err &&
+ test_must_be_empty out &&
+ test_must_be_empty err &&
+ test_cmp_config -C base 1 core.repositoryFormatVersion &&
+ test_cmp_config -C base true extensions.worktreeConfig
+'
+
+test_expect_success 'relocates core.worktree' '
+ reset_config_when_finished &&
+ mkdir dir &&
+ git -C base config core.worktree ../../dir &&
+ git -C base worktree init-worktree-config >out 2>err &&
+ test_must_be_empty out &&
+ test_must_be_empty err &&
+ test_cmp_config -C base 1 core.repositoryFormatVersion &&
+ test_cmp_config -C base true extensions.worktreeConfig &&
+ test_cmp_config -C base ../../dir core.worktree &&
+ test_must_fail git -C worktree core.worktree
+'
+
+test_expect_success 'relocates core.bare' '
+ reset_config_when_finished &&
+ git -C base config core.bare true &&
+ git -C base worktree init-worktree-config >out 2>err &&
+ test_must_be_empty out &&
+ test_must_be_empty err &&
+ test_cmp_config -C base 1 core.repositoryFormatVersion &&
+ test_cmp_config -C base true extensions.worktreeConfig &&
+ test_cmp_config -C base true core.bare &&
+ test_must_fail git -C worktree core.bare
+'
+
+test_expect_success 'skips upgrade is already upgraded' '
+ reset_config_when_finished &&
+ git -C base worktree init-worktree-config &&
+ git -C base config core.bare true &&
+
+ # this should be a no-op, even though core.bare
+ # makes the worktree be broken.
+ git -C base worktree init-worktree-config >out 2>err &&
+ test_must_be_empty out &&
+ test_must_be_empty err &&
+ test_must_fail git -C base config --worktree core.bare &&
+ git -C base config core.bare
+'
+
+test_done--
gitgitgadget