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

[PATCH v3 4/6] worktree: new config file hierarchy

From
Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Date
Jan 26, 2016, 11:44 UTC
Message-ID
<1453808685-21235-5-git-send-email-pclouds@gmail.com>
In-Reply-To
<1453808685-21235-1-git-send-email-pclouds@gmail.com>
($C stands for $GIT_COMMON_DIR for the rest of the message)
In main worktree, we read these config files in this order:
1) system config
2) XDG config
3) user config
4) $GIT_DIR/config

Currently linked worktrees share the same config file at step 4 with main worktree. The problem is, not all config variables can be shared. We need per-repo vars and per-worktree ones.

With this patch, since worktree v1, the repo config file (or worktree config file in multi worktree context) is no longer shared. Main worktree reads $C/config. Linked worktrees read $C/worktrees/xxx/config and a new file, $C/worktrees/config. Sharing is done via this new file. The read hierarchy for a worktree becomes

1) system config
2) XDG config
3) user config
4) $C/common/config
5) $C/worktrees/xxx/config (or $C/config for main worktree)

Compare to an alternative scheme where $C/config contains both shared variables and main-worktree-only ones, this is a cleaner design.

* We do not have to check every single variable name to see if it's
  shared or per-worktree when reading config files.
* We do not enforce any particular variable split. If a variable
  is in $C/worktrees/config, it is shared. Putting core.worktree in
  $C/worktrees/config is punished the same way the variable is put in
  $HOME/.gitconfig, for example.
* We will provide a new "git config --repo" to access this new config
  file. In single-worktree context, or worktree v0, --repo is an alias
  of --local.

There is one problem though. We store worktree version in config file and expect that all worktrees must share the same version (i.e. read the same config file). But the share-ness of per-repo config files is changed based on worktree version. Where do we put extensions.worktree then?

Worktree version detection is tweaked a bit in this case. The presence of $C/worktrees/config implies worktree version 1+. The lack of it implies wortree version 0.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/config.txt               |  5 +++--
 Documentation/git-worktree.txt         |  8 ++++++-
 Documentation/gitrepository-layout.txt |  8 +++++++
 cache.h                                |  4 +++-
 config.c                               | 14 ++++++++++--
 path.c                                 |  1 -
 setup.c                                | 37 +++++++++++++++++++++++++------
 t/t2028-worktree-config.sh (new +x)    | 40 ++++++++++++++++++++++++++++++++++
 8 files changed, 103 insertions(+), 14 deletions(-)
 create mode 100755 t/t2028-worktree-config.sh
diff --git a/Documentation/config.txt b/Documentation/config.txt
index 07f7a3b..5009d98 100644
--- a/Documentation/config.txt
+++ b/Documentation/config.txt
@@ -2,8 +2,9 @@ CONFIGURATION FILE
 ------------------
 
 The Git configuration file contains a number of variables that affect
-the Git commands' behavior. The `.git/config` file in each repository
-is used to store the configuration for that repository, and
+the Git commands' behavior. The files `.git/config` and
+`.git/common/config` (see linkgit:git-worktree.txt[1]) are each
+repository is used to store the configuration for that repository, and
 `$HOME/.gitconfig` is used to store a per-user configuration as
 fallback values for the `.git/config` file. The file `/etc/gitconfig`
 can be used to store a system-wide default configuration.
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index 048d7d6..0846f2a 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -111,7 +111,13 @@ WORKTREE VERSIONS AND MIGRATION
 Multiple worktree is still an experimental feature and evolving. Every
 time the behavior is changed, the "worktree version" is stepped
 up. Worktree version is stored as a configuration variable
-extensions.worktree.
+extensions.worktree. Latest version is one.
+
+Version 1
+~~~~~~~~~
+In this version, the repository config file `.git/config` is not
+shared anymore. A new file, `.git/common/config`, read for all
+worktrees. Shared configuration should be stored here.
 
 Version 0
 ~~~~~~~~~
diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt
index 771e362..d65345d 100644
--- a/Documentation/gitrepository-layout.txt
+++ b/Documentation/gitrepository-layout.txt
@@ -261,6 +261,11 @@ worktrees::
 	$GIT_COMMON_DIR is set, in which case
 	"$GIT_COMMON_DIR/worktrees" will be used instead.
 
+common/config::
+	Repository specific configuration file. Note that if this file
+	is present, it must contain the variable extensions.worktree
+	whose value must be one or above.
+
 worktrees/<id>/gitdir::
 	A text file containing the absolute path back to the .git file
 	that points to here. This is used to check if the linked
@@ -280,6 +285,9 @@ worktrees/<id>/link::
 	file. It is used to detect if the linked repository is
 	manually removed.
 
+worktrees/<id>/config::
+	Working tree specific configuration file.
+
 SEE ALSO
 --------
 linkgit:git-init[1],
diff --git a/cache.h b/cache.h
index e742c46..fa5e451 100644
--- a/cache.h
+++ b/cache.h
@@ -1497,7 +1497,9 @@ extern void git_config(config_fn_t fn, void *);
 extern int git_config_with_options(config_fn_t fn, void *,
 				   struct git_config_source *config_source,
 				   int respect_includes);
-extern int git_config_early(config_fn_t fn, void *, const char *repo_config);
+extern int git_config_early(config_fn_t fn, void *,
+			    const char *repo_common_config,
+			    const char *repo_config);
 extern int git_parse_ulong(const char *, unsigned long *);
 extern int git_parse_maybe_bool(const char *);
 extern int git_config_int(const char *, const char *);
diff --git a/config.c b/config.c
index 86a5eb2..a75926e 100644
--- a/config.c
+++ b/config.c
@@ -1184,7 +1184,9 @@ int git_config_system(void)
 	return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0);
 }
 
-int git_config_early(config_fn_t fn, void *data, const char *repo_config)
+int git_config_early(config_fn_t fn, void *data,
+		     const char *repo_common_config,
+		     const char *repo_config)
 {
 	int ret = 0, found = 0;
 	char *xdg_config = xdg_config_home("config");
@@ -1206,6 +1208,11 @@ int git_config_early(config_fn_t fn, void *data, const char *repo_config)
 		found += 1;
 	}
 
+	if (repo_common_config && !access_or_die(repo_common_config, R_OK, 0)) {
+		ret += git_config_from_file(fn, repo_common_config, data);
+		found += 1;
+	}
+
 	if (repo_config && !access_or_die(repo_config, R_OK, 0)) {
 		ret += git_config_from_file(fn, repo_config, data);
 		found += 1;
@@ -1231,6 +1238,7 @@ int git_config_with_options(config_fn_t fn, void *data,
 			    struct git_config_source *config_source,
 			    int respect_includes)
 {
+	struct strbuf repo_common_config = STRBUF_INIT;
 	char *repo_config = NULL;
 	int ret;
 	struct config_include_data inc = CONFIG_INCLUDE_INIT;
@@ -1253,10 +1261,12 @@ int git_config_with_options(config_fn_t fn, void *data,
 	else if (config_source && config_source->blob)
 		return git_config_from_blob_ref(fn, config_source->blob, data);
 
+	strbuf_addf(&repo_common_config, "%s/common/config", get_git_common_dir());
 	repo_config = git_pathdup("config");
-	ret = git_config_early(fn, data, repo_config);
+	ret = git_config_early(fn, data, repo_common_config.buf, repo_config);
 	if (repo_config)
 		free(repo_config);
+	strbuf_release(&repo_common_config);
 	return ret;
 }
 
diff --git a/path.c b/path.c
index 3b85968..aee9f59 100644
--- a/path.c
+++ b/path.c
@@ -140,7 +140,6 @@ static struct common_dir common_list_v1[] = {
 	{ 0, 1, 0, "worktrees" },
 	{ 0, 1, 0, "rr-cache" },
 	{ 0, 1, 0, "svn" },
-	{ 0, 0, 0, "config" },
 	{ 1, 0, 0, "gc.pid" },
 	{ 0, 0, 0, "packed-refs" },
 	{ 0, 0, 0, "shallow" },
diff --git a/setup.c b/setup.c
index 2f41648..7ca8ebe 100644
--- a/setup.c
+++ b/setup.c
@@ -385,18 +385,32 @@ static int check_repo_format(const char *var, const char *value, void *cb)
 static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 {
 	struct strbuf sb = STRBUF_INIT;
+	struct strbuf sb2 = STRBUF_INIT;
+	struct strbuf sb3 = STRBUF_INIT;
 	const char *repo_config;
+	const char *repo_common_config;
 	config_fn_t fn;
-	int ret = 0;
+	int ret = 0, has_common_dir;
 
 	string_list_clear(&unknown_extensions, 0);
 
-	if (get_common_dir(&sb, gitdir))
-		fn = check_repo_format;
-	else
+	has_common_dir = get_common_dir(&sb, gitdir);
+	strbuf_addf(&sb2, "%s/common/config", sb.buf);
+
+	if (access(sb2.buf, F_OK) == -1) { /* worktree v0 */
+		if (has_common_dir)
+			fn = check_repo_format;
+		else
+			fn = check_repository_format_version;
+		strbuf_addf(&sb3, "%s/config", sb.buf);
+		repo_common_config = NULL;
+		repo_config = sb3.buf;
+	} else {
 		fn = check_repository_format_version;
-	strbuf_addstr(&sb, "/config");
-	repo_config = sb.buf;
+		strbuf_addf(&sb3, "%s/config", gitdir);
+		repo_common_config = sb2.buf;
+		repo_config = sb3.buf;
+	}
 
 	/*
 	 * git_config() can't be used here because it calls git_pathdup()
@@ -407,7 +421,7 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 	 * Use a gentler version of git_config() to check if this repo
 	 * is a good one.
 	 */
-	git_config_early(fn, NULL, repo_config);
+	git_config_early(fn, NULL, repo_common_config, repo_config);
 	if (GIT_REPO_VERSION_READ < repository_format_version) {
 		if (!nongit_ok)
 			die ("Expected git repo version <= %d, found %d",
@@ -433,7 +447,16 @@ static int check_repository_format_gently(const char *gitdir, int *nongit_ok)
 		ret = -1;
 	}
 
+	if (repo_common_config && repository_format_worktree_version < 1)
+		die(_("worktree version must be one minimum in the presence of %s"),
+		    repo_common_config);
+	if (!repo_common_config && repository_format_worktree_version > 0)
+		die(_("worktree version must be zero if %s/common/config does not exist"),
+		    gitdir);
+
 	strbuf_release(&sb);
+	strbuf_release(&sb2);
+	strbuf_release(&sb3);
 	return ret;
 }
 
diff --git a/t/t2028-worktree-config.sh b/t/t2028-worktree-config.sh
new file mode 100755
index 0000000..5561788
--- /dev/null
+++ b/t/t2028-worktree-config.sh
@@ -0,0 +1,40 @@
+#!/bin/sh
+
+test_description="config file in multi worktree"
+
+. ./test-lib.sh
+
+test_expect_success 'setup' '
+	test_commit start &&
+	git worktree add wt1 &&
+	git worktree add wt2
+'
+
+test_expect_success 'main config is shared in version 0' '
+	git config -f .git/config wt.name main &&
+	git config wt.name >actual &&
+	echo main >expected &&
+	test_cmp expected actual &&
+	git -C wt1 config wt.name >actual &&
+	test_cmp expected actual
+'
+
+test_expect_success 'main config is for main worktree only (v1)' '
+	mkdir .git/common &&
+	git config -f .git/common/config extensions.worktree 1 &&
+	git config wt.name >actual &&
+	echo main >expected &&
+	test_cmp expected actual &&
+	test_must_fail git -C wt1 config wt.name
+'
+
+test_expect_success 'worktrees/config is shared (v1)' '
+	git config -f .git/common/config some.thing is-shared &&
+	echo is-shared >expected &&
+	git config some.thing >actual &&
+	test_cmp expected actual &&
+	git -C wt1 config some.thing >actual &&
+	test_cmp expected actual
+'
+
+test_done
-- 
2.7.0.288.g1d8ad15
Previous: Nguyễn Thái Ngọc DuyNext: Junio C Hamano
Message 41 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.