git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v3 6/6] worktree add: switch to worktree version 1

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Jan 26, 2016, 11:44 UTC
Message-ID
<1453808685-21235-7-git-send-email-pclouds@gmail.com>
In-Reply-To
<1453808685-21235-1-git-send-email-pclouds@gmail.com>

The most obvious use case is, "git worktree add" creates the first linked worktree. In this case, we should be able to move to latest worktree version. The following happens:

 - common/config is created with extensions.worktree 1 and
   core.repositoryformatversion 1
 - all config keys except a few per-worktree are moved to
   common/config
 - per-worktree keys stay with the main worktree's config file
 - the main worktree config file also has worktree version explicitly
   set to 1. This is to prevent older Git binaries from reading it.

What if the repo already has another linked worktree and the user wants to stay at version 0, maybe because multiple git binaries can access this repo? "worktree add --version=0" can be used, but it's really not recommended to stay at lower (and buggy) version.

A note about core.bare staying per-worktree. On the surface it does not make sense for core.bare to be worktree specific. It's made so in order to "grow" new worktrees from a bare repo. In these new linked worktrees, core.bare will be hidden away and worktree-related commands won't complain about bare repository.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-worktree.txt |  9 +++-
 builtin/worktree.c             | 95 ++++++++++++++++++++++++++++++++++++++++++
 t/t2028-worktree-config.sh     | 47 ++++++++++++++++++++-
 3 files changed, 148 insertions(+), 3 deletions(-)
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 6082d4d..0d7d523 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -9,7 +9,7 @@ git-worktree - Manage multiple working trees
 SYNOPSIS
 --------
 [verse]
-'git worktree add' [-f] [--detach] [-b <new-branch>] <path> [<branch>]
+'git worktree add' [-f] [--detach] [-b <new-branch>] [--version=<N>] <path> [<branch>]
 'git worktree prune' [-n] [-v] [--expire <expire>]
 'git worktree list' [--porcelain]
 
@@ -89,6 +89,13 @@ OPTIONS
 	With `add`, detach HEAD in the new working tree. See "DETACHED HEAD"
 	in linkgit:git-checkout[1].
 
+--version=<N>::
+	By default when a new working directory is added, worktree
+	layout is automatically migrated to latest version. This
+	option can be used to specify only migrate to the specified
+	version, or no migrate at all if it's already current worktree
+	version.
+
 -n::
 --dry-run::
 	With `prune`, do not remove anything; just report what it would
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 475b958..551fe37 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -140,6 +140,7 @@ static char *junk_work_tree;
 static char *junk_git_dir;
 static int is_junk;
 static pid_t junk_pid;
+static int target_version = 1;
 
 static void remove_junk(void)
 {
@@ -184,6 +185,93 @@ static const char *worktree_basename(const char *path, int *olen)
 	return name;
 }
 
+struct key_data {
+	const char *key;
+	char *value;
+};
+
+static int get_one_key(const char *key, const char *value, void *cb)
+{
+	struct key_data *kd = cb;
+
+	if (!strcmp(key, kd->key))
+		kd->value = xstrdup(value);
+
+	return 0;
+}
+
+static char *get_key(const char *file, const char *key)
+{
+	struct key_data kd;
+
+	kd.key = key;
+	kd.value = NULL;
+	if (git_config_from_file(get_one_key, file, &kd))
+		return NULL;
+	return kd.value;
+}
+
+static void migrate_worktree_layout(void)
+{
+	const char *per_wortree_keys[] = {
+		"core.bare",
+		"core.ignorestat",
+		"core.sparsecheckout",
+		"core.worktree",
+		NULL
+	};
+	struct strbuf sb = STRBUF_INIT;
+	const char **key_p;
+
+	switch (repository_format_worktree_version) {
+	case 0:
+		strbuf_addf(&sb, "%s/common", get_git_common_dir());
+		if (mkdir_in_gitdir(sb.buf))
+			die_errno(_("failed to create directory %s"), sb.buf);
+		if (repository_format_version < 1 &&
+		    git_config_set("core.repositoryformatversion", "1"))
+			die(_("failed to set core.repositoryformatversion to one"));
+		if (git_config_set("extensions.worktree", "1"))
+			die(_("failed to set extensions.worktree to one"));
+		strbuf_addstr(&sb, "/config");
+		if (rename(git_path("config"), sb.buf))
+			die_errno(_("failed to set move config file to %s"),
+				  sb.buf);
+		for (key_p = per_wortree_keys; *key_p; key_p++) {
+			const char *key = *key_p;
+			char *value = get_key(sb.buf, key);
+
+			if (value) {
+				if (git_config_set(key, value))
+					die(_("failed to keep %s in main worktree's config file"), key);
+				if (git_config_set_in_file(sb.buf, key, NULL))
+					die(_("failed to delete %s in shared config file"), key);
+				free(value);
+			}
+		}
+
+		/*
+		 * we're still in version 0 in this process, this will
+		 * create a new file $GIT_COMMON_DIR/config with only
+		 * one key, extensions.worktree. This will force old
+		 * git binaries that do not understand v1 to bail out.
+		 */
+		if (repository_format_version < 1 &&
+		    git_config_set("core.repositoryformatversion", "1"))
+			die(_("failed to set core.repositoryformatversion to one"));
+		if (git_config_set("extensions.worktree", "1"))
+			die(_("failed to set extensions.worktree to one"));
+
+		repository_format_worktree_version = 1;
+		break;
+	case 1:
+		break;
+	default:
+		die(_("unsupported worktree format version %d"),
+		    repository_format_worktree_version);
+	}
+}
+
 static int add_worktree(const char *path, const char *refname,
 			const struct add_opts *opts)
 {
@@ -297,6 +385,9 @@ static int add_worktree(const char *path, const char *refname,
 		free(junk_git_dir);
 		junk_work_tree = NULL;
 		junk_git_dir = NULL;
+
+		while (repository_format_worktree_version < target_version)
+			migrate_worktree_layout();
 	}
 done:
 	strbuf_reset(&sb);
@@ -322,6 +413,8 @@ static int add(int ac, const char **av, const char *prefix)
 		OPT_STRING('B', NULL, &new_branch_force, N_("branch"),
 			   N_("create or reset a branch")),
 		OPT_BOOL(0, "detach", &opts.detach, N_("detach HEAD at named commit")),
+		OPT_INTEGER(0, "version", &target_version,
+			   N_("stay at this worktree version")),
 		OPT_END()
 	};
 
@@ -331,6 +424,8 @@ static int add(int ac, const char **av, const char *prefix)
 		die(_("-b, -B, and --detach are mutually exclusive"));
 	if (ac < 1 || ac > 2)
 		usage_with_options(worktree_usage, options);
+	if (target_version < 0 || target_version > 1)
+		die(_("invalid worktree version %d"), target_version);
 
 	path = prefix ? prefix_filename(prefix, strlen(prefix), av[0]) : av[0];
 	branch = ac < 2 ? "HEAD" : av[1];
diff --git a/t/t2028-worktree-config.sh b/t/t2028-worktree-config.sh
index d11b2ce..0d4cb8e 100755
--- a/t/t2028-worktree-config.sh
+++ b/t/t2028-worktree-config.sh
@@ -6,8 +6,8 @@ test_description="config file in multi worktree"
 
 test_expect_success 'setup' '
 	test_commit start &&
-	git worktree add wt1 &&
-	git worktree add wt2
+	git worktree add --version=0 wt1 &&
+	git worktree add --version=0 wt2
 '
 
 test_expect_success 'main config is shared in version 0' '
@@ -58,4 +58,47 @@ test_expect_success 'config --repo on v1' '
 	test_cmp expected actual
 '
 
+test_expect_success 'prepare worktree v0' '
+	test_create_repo repo-v0 &&
+	(
+		cd repo-v0 &&
+		test_commit v0 &&
+		git config core.sparsecheckout true &&
+		git config core.ignorestat true &&
+		git config core.worktree "$TEST_DIRECTORY" &&
+		git config share.key value
+	)
+'
+
+test_expect_success 'migrate v0 to v1' '
+	git -C repo-v0 worktree add --version=1 wt
+'
+
+test_expect_success 'after migration: main wortree has extensions.worktree' '
+	test "`git -C repo-v0 config core.repositoryformatversion`" = 1 &&
+	test "`git -C repo-v0 config extensions.worktree`" = 1
+'
+
+test_expect_success 'after migration: linked wortree has extensions.worktree' '
+	test "`git -C repo-v0/wt config core.repositoryformatversion`" = 1 &&
+	test "`git -C repo-v0/wt config extensions.worktree`" = 1
+'
+
+test_expect_success 'after migration: main wortree keeps per-worktree vars' '
+	test "`git -C repo-v0 config core.sparsecheckout`" = true &&
+	test "`git -C repo-v0 config core.ignorestat`" = true &&
+	test "`git -C repo-v0 config core.worktree`" = "$TEST_DIRECTORY"
+'
+
+test_expect_success 'after migration: linked wortree has no per-worktree vars' '
+	test_must_fail git -C repo-v0/wt config core.sparsecheckout &&
+	test_must_fail git -C repo-v0/wt config core.ignorestat &&
+	test_must_fail git -C repo-v0/wt config core.worktree
+'
+
+test_expect_success 'after migration: shared vars are shared' '
+	test "`git -C repo-v0 config share.key`" = value &&
+	test "`git -C repo-v0/wt config share.key`" = value
+'
+
 test_done
-- 
2.7.0.288.g1d8ad15
Previous: Duy NguyenNext: Max Kirillov
Message 49 of 53 in “Split .git/config in multiple worktree setup”
  1. 0/5 Split .git/config in multiple worktree setupNguyễn Thái Ngọc Duy, Dec 2, 2015
  2. 1/5 dir.c: clean the entire struct in clear_exclude_list()Nguyễn Thái Ngọc Duy, Dec 2, 2015
  3. 2/5 config.c: move worktree-specific variables to .git/worktrees/...Nguyễn Thái Ngọc Duy, Dec 2, 2015
  4. Eric SunshineDec 6, 2015
  5. Duy NguyenDec 6, 2015
  6. 3/5 setup.c: remove special case of core.worktree and core.bareNguyễn Thái Ngọc Duy, Dec 2, 2015
  7. 4/5 worktree: make core.sparseCheckout and core.ignoreStat per-worktreeNguyễn Thái Ngọc Duy, Dec 2, 2015
  8. 5/5 git-worktree.txt: mention about the config file splitNguyễn Thái Ngọc Duy, Dec 2, 2015
  9. Eric SunshineDec 6, 2015
  10. Max KirillovDec 3, 2015
  11. Duy NguyenDec 3, 2015
  12. Junio C HamanoDec 3, 2015
  13. Max KirillovDec 3, 2015
  14. Max KirillovDec 3, 2015
  15. Duy NguyenDec 4, 2015
  16. 0/6 Split .git/config in multiple worktree setupNguyễn Thái Ngọc Duy, Dec 27, 2015
  17. 1/6 Define new repo extension to manage multiple worktree behaviorsNguyễn Thái Ngọc Duy, Dec 27, 2015
  18. 2/6 config.c: move worktree-specific variables to .git/worktrees/...Nguyễn Thái Ngọc Duy, Dec 27, 2015
  19. 3/6 setup.c: remove special case of core.worktree and core.bareNguyễn Thái Ngọc Duy, Dec 27, 2015
  20. 4/6 worktree: make core.sparseCheckout and core.ignoreStat per-worktreeNguyễn Thái Ngọc Duy, Dec 27, 2015
  21. 5/6 config.c: allow to un-share certain config in multi-worktree setupNguyễn Thái Ngọc Duy, Dec 27, 2015
  22. 6/6 worktree: bump worktree version to 1 on "worktree add"Nguyễn Thái Ngọc Duy, Dec 27, 2015
  23. Max KirillovJan 11, 2016
  24. 0/6 Split .git/config in multiple worktree setupNguyễn Thái Ngọc Duy, Jan 26, 2016
  25. 1/6 worktree: new repo extension to manage worktree behaviorsNguyễn Thái Ngọc Duy, Jan 26, 2016
  26. Junio C HamanoJan 27, 2016
  27. Duy NguyenJan 28, 2016
  28. Max KirillovJan 30, 2016
  29. Junio C HamanoJan 31, 2016
  30. Stefan MonnierFeb 1, 2016
  31. Stefan MonnierFeb 1, 2016
  32. Duy NguyenFeb 1, 2016
  33. Junio C HamanoFeb 1, 2016
  34. git worktree (was: [PATCH v3 1/6] worktree: new repo extension to manage worktree behaviors)Stefan Monnier, Feb 4, 2016
  35. Dennis KaarsemakerFeb 1, 2016
  36. Max KirillovJan 30, 2016
  37. 2/6 path.c: new (identical) list for worktree v1Nguyễn Thái Ngọc Duy, Jan 26, 2016
  38. Junio C HamanoJan 27, 2016
  39. Max KirillovJan 30, 2016
  40. 3/6 worktree: share .git/common in v1Nguyễn Thái Ngọc Duy, Jan 26, 2016
  41. 4/6 worktree: new config file hierarchyNguyễn Thái Ngọc Duy, Jan 26, 2016
  42. Junio C HamanoJan 27, 2016
  43. Duy NguyenJan 28, 2016
  44. Junio C HamanoJan 28, 2016
  45. Duy NguyenFeb 1, 2016
  46. 5/6 config: select .git/common/config with --repoNguyễn Thái Ngọc Duy, Jan 26, 2016
  47. Max KirillovJan 30, 2016
  48. Duy NguyenFeb 1, 2016
  49. 6/6 worktree add: switch to worktree version 1Nguyễn Thái Ngọc Duy, Jan 26, 2016
  50. Max KirillovFeb 1, 2016
  51. Duy NguyenFeb 1, 2016
  52. Max KirillovFeb 2, 2016
  53. Junio C HamanoJan 27, 2016

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.