threads / patch / 50113

patch, 6 partsAdd "git config --move-to"

Subject: [PATCH 0/6] Add "git config --move-to"

## tl;dr

12 messages between Dec 27, 2018 and May 1, 2019. Diffs are folded; open one to read it.

replies: 11people: 4as markdown or json

Nguyễn Thái Ngọc Duy· Dec 27, 2018, 15:56 UTC · lore

This is a continuation of nd/per-worktree-config, fixing is small things from that series and prepares to make submodules work in multiple worktrees.

The first thing is adding API for updating per-worktree config in 03/06. When submodule code wants to write to a per-worktree config, calling this function takes care of both single and multi worktree setups, similar to "git config --worktree <name> <value>"

The second thing is "git config --move-to" and friends. This helps the config transition right after enabling extensions.worktreeConfig, when the user has to make sure per-worktree config stays in per-worktree file.

Manual moving config from one file to another is possible with a couple keys like core.worktree, but once you have to move multiple groups (i.e. "submodule.*.*" keys) then it becomes a lot less fun and more pain. This simplifies that work (and also migration documentation)

Note that I added --move-glob-to because I think glob fits much better than regex for matching keys because of the dots in config keys. I added --move-regex-to anyway because we have --get-regexp. Maybe I should add --get-glob too (but probably not now).

There is also a NEEDSWORK in 03/06. But I hope it could be addressed after this series is merged (so that I could continue pushing submodule changes out)

Nguyễn Thái Ngọc Duy (6):
  config.c: avoid git_path() in do_git_config_sequence()
  worktree.c: add get_worktree_config()
  config.c: add repo_config_set_worktree_gently()
  config: use OPT_FILENAME()
  config: factor out set_config_source_file()
  config: add --move-to
 Documentation/git-config.txt   |  12 ++
 Documentation/git-worktree.txt |  16 ++-
 builtin/config.c               | 244 ++++++++++++++++++++++++---------
 config.c                       |  55 +++++++-
 config.h                       |   3 +
 t/t1300-config.sh              |  54 ++++++++
 worktree.c                     |  16 +++
 worktree.h                     |   7 +
 8 files changed, 330 insertions(+), 77 deletions(-)
-- 
2.20.0.482.g66447595a7
Nguyễn Thái Ngọc Duy· Dec 27, 2018, 15:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 1/6] config.c: avoid git_path() in do_git_config_sequence()

This function has both $GIT_COMMON_DIR and $GIT_DIR in "opts". Use it to construct config.worktree path instead because git_pathdup() is tied to the current worktree, but the given $GIT_DIR could be from another one.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 config.c | 14 ++++++++------
 1 file changed, 8 insertions(+), 6 deletions(-)
Show changes to config.c +8 −6
diff --git a/config.c b/config.c
index ff521eb27a..79fbe65da8 100644
--- a/config.c
+++ b/config.c
@@ -1665,6 +1665,7 @@ static int do_git_config_sequence(const struct config_options *opts,
 	char *xdg_config = xdg_config_home("config");
 	char *user_config = expand_user_path("~/.gitconfig", 0);
 	char *repo_config;
+	char *worktree_config;
 
 	if (opts->commondir)
 		repo_config = mkpathdup("%s/config", opts->commondir);
@@ -1672,6 +1673,10 @@ static int do_git_config_sequence(const struct config_options *opts,
 		BUG("git_dir without commondir");
 	else
 		repo_config = NULL;
+	if (repository_format_worktree_config)
+		worktree_config = mkpathdup("%s/config.worktree", opts->git_dir);
+	else
+		worktree_config = NULL;
 
 	current_parsing_scope = CONFIG_SCOPE_SYSTEM;
 	if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
@@ -1693,12 +1698,8 @@ static int do_git_config_sequence(const struct config_options *opts,
 	 * Note: this should have a new scope, CONFIG_SCOPE_WORKTREE.
 	 * But let's not complicate things before it's actually needed.
 	 */
-	if (repository_format_worktree_config) {
-		char *path = git_pathdup("config.worktree");
-		if (!access_or_die(path, R_OK, 0))
-			ret += git_config_from_file(fn, path, data);
-		free(path);
-	}
+	if (worktree_config && !access_or_die(worktree_config, R_OK, 0))
+		ret += git_config_from_file(fn, worktree_config, data);
 
 	current_parsing_scope = CONFIG_SCOPE_CMDLINE;
 	if (git_config_from_parameters(fn, data) < 0)
@@ -1708,6 +1709,7 @@ static int do_git_config_sequence(const struct config_options *opts,
 	free(xdg_config);
 	free(user_config);
 	free(repo_config);
+	free(worktree_config);
 	return ret;
 }
 
-- 
2.20.0.482.g66447595a7
Junio C Hamano· Jan 10, 2019, 21:38 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 1/6] config.c: avoid git_path() in do_git_config_sequence()

Nguyễn Thái Ngọc Duy  <pclouds@gmail.com> writes:
> This function has both $GIT_COMMON_DIR and $GIT_DIR in "opts". Use it
> to construct config.worktree path instead because git_pathdup() is
> tied to the current worktree, but the given $GIT_DIR could be from
> another one.
Given that git_pathdup() does
	do_git_path(the_repository, NULL, pathname, fmt, args);

I am not sure what you mean by "tied to the current worktree" above. As do_git_path() uses strbuf_worktree_gitdir() that does

	if (!wt)
		strbuf_addstr(buf, repo->gitdir);

you meant s/current worktree/current repository/, perhaps? In which case I think the description makes sense to me.

Thanks.
Show 51 quoted lines
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
> ---
>  config.c | 14 ++++++++------
>  1 file changed, 8 insertions(+), 6 deletions(-)
>
> diff --git a/config.c b/config.c
> index ff521eb27a..79fbe65da8 100644
> --- a/config.c
> +++ b/config.c
> @@ -1665,6 +1665,7 @@ static int do_git_config_sequence(const struct config_options *opts,
>  	char *xdg_config = xdg_config_home("config");
>  	char *user_config = expand_user_path("~/.gitconfig", 0);
>  	char *repo_config;
> +	char *worktree_config;
>  
>  	if (opts->commondir)
>  		repo_config = mkpathdup("%s/config", opts->commondir);
> @@ -1672,6 +1673,10 @@ static int do_git_config_sequence(const struct config_options *opts,
>  		BUG("git_dir without commondir");
>  	else
>  		repo_config = NULL;
> +	if (repository_format_worktree_config)
> +		worktree_config = mkpathdup("%s/config.worktree", opts->git_dir);
> +	else
> +		worktree_config = NULL;
>  
>  	current_parsing_scope = CONFIG_SCOPE_SYSTEM;
>  	if (git_config_system() && !access_or_die(git_etc_gitconfig(), R_OK, 0))
> @@ -1693,12 +1698,8 @@ static int do_git_config_sequence(const struct config_options *opts,
>  	 * Note: this should have a new scope, CONFIG_SCOPE_WORKTREE.
>  	 * But let's not complicate things before it's actually needed.
>  	 */
> -	if (repository_format_worktree_config) {
> -		char *path = git_pathdup("config.worktree");
> -		if (!access_or_die(path, R_OK, 0))
> -			ret += git_config_from_file(fn, path, data);
> -		free(path);
> -	}
> +	if (worktree_config && !access_or_die(worktree_config, R_OK, 0))
> +		ret += git_config_from_file(fn, worktree_config, data);
>  
>  	current_parsing_scope = CONFIG_SCOPE_CMDLINE;
>  	if (git_config_from_parameters(fn, data) < 0)
> @@ -1708,6 +1709,7 @@ static int do_git_config_sequence(const struct config_options *opts,
>  	free(xdg_config);
>  	free(user_config);
>  	free(repo_config);
> +	free(worktree_config);
>  	return ret;
>  }
Nguyễn Thái Ngọc Duy· Dec 27, 2018, 15:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 2/6] worktree.c: add get_worktree_config()

"git config --worktree" can write to the write file whether extensions.worktreeConfig is enabled or not. In order to do the same using config API, we need to determine the right file to write to. Add this function for that purpose. This is the basis for the coming repo_config_set_worktree()

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/config.c |  9 ++-------
 worktree.c       | 16 ++++++++++++++++
 worktree.h       |  7 +++++++
 3 files changed, 25 insertions(+), 7 deletions(-)
Show changes to 3 files +25 −7

builtin/config.c, worktree.c, worktree.h

diff --git a/builtin/config.c b/builtin/config.c
index 84385ef165..771cfa54bd 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -650,18 +650,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 	else if (use_local_config)
 		given_config_source.file = git_pathdup("config");
 	else if (use_worktree_config) {
-		struct worktree **worktrees = get_worktrees(0);
-		if (repository_format_worktree_config)
-			given_config_source.file = git_pathdup("config.worktree");
-		else if (worktrees[0] && worktrees[1])
+		given_config_source.file = get_worktree_config(the_repository);
+		if (!given_config_source.file)
 			die(_("--worktree cannot be used with multiple "
 			      "working trees unless the config\n"
 			      "extension worktreeConfig is enabled. "
 			      "Please read \"CONFIGURATION FILE\"\n"
 			      "section in \"git help worktree\" for details"));
-		else
-			given_config_source.file = git_pathdup("config");
-		free_worktrees(worktrees);
 	} else if (given_config_source.file) {
 		if (!is_absolute_path(given_config_source.file) && prefix)
 			given_config_source.file =
diff --git a/worktree.c b/worktree.c
index d6a0ee7f73..d335bdf28a 100644
--- a/worktree.c
+++ b/worktree.c
@@ -581,3 +581,19 @@ int other_head_refs(each_ref_fn fn, void *cb_data)
 	free_worktrees(worktrees);
 	return ret;
 }
+
+char *get_worktree_config(struct repository *r)
+{
+	struct worktree **worktrees = get_worktrees(0);
+	char *path;
+
+	if (repository_format_worktree_config)
+		path = repo_git_path(r, "config.worktree");
+	else if (worktrees[0] && worktrees[1])
+		path = NULL;
+	else
+		path = repo_git_path(r, "config");
+
+	free_worktrees(worktrees);
+	return path;
+}
diff --git a/worktree.h b/worktree.h
index 9e3b0b7b6f..4c41002d31 100644
--- a/worktree.h
+++ b/worktree.h
@@ -132,4 +132,11 @@ void strbuf_worktree_ref(const struct worktree *wt,
 const char *worktree_ref(const struct worktree *wt,
 			 const char *refname);
 
+/*
+ * Return the path to config file that can contain worktree-specific
+ * config (or NULL in unsupported setups). The caller must free the
+ * return value.
+ */
+char *get_worktree_config(struct repository *r);
+
 #endif
-- 
2.20.0.482.g66447595a7
Eric Sunshine· Dec 27, 2018, 20:36 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 2/6] worktree.c: add get_worktree_config()

On Thu, Dec 27, 2018 at 10:56 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> "git config --worktree" can write to the write file whether
s/write file/right file/
Show 6 quoted lines
> extensions.worktreeConfig is enabled or not. In order to do the same
> using config API, we need to determine the right file to write to. Add
> this function for that purpose. This is the basis for the coming
> repo_config_set_worktree()
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Nguyễn Thái Ngọc Duy· Dec 27, 2018, 15:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 3/6] config.c: add repo_config_set_worktree_gently()

This is C equivalent of "git config --set --worktree". In other words, it will

- write to $GIT_DIR/config in single-worktree setup
- write to $GIT_COMMON_DIR/worktrees/<x>/config.worktree or
  $GIT_COMMON_DIR/config.worktree (for main worktree)
  if extensions.worktreeConfig is enabled
- return error in multiple-worktree setup if extensions.worktreeConfig
  is not enabled.

While at there, also add repo_config_set*() for writing to $GIT_COMMON_DIR/config.

Note, since git_config_set_multivar_in_file_gently() only invalidates the config set of the_repository, anybody who uses these functions on submodules must do additional invalidation if needed.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 config.c | 41 ++++++++++++++++++++++++++++++++++++++++-
 config.h |  3 +++
 2 files changed, 43 insertions(+), 1 deletion(-)
Show changes to 2 files +43 −1

config.c, config.h

diff --git a/config.c b/config.c
index 79fbe65da8..151d28664e 100644
--- a/config.c
+++ b/config.c
@@ -19,6 +19,7 @@
 #include "utf8.h"
 #include "dir.h"
 #include "color.h"
+#include "worktree.h"
 
 struct config_source {
 	struct config_source *prev;
@@ -2137,6 +2138,39 @@ int repo_config_get_pathname(struct repository *repo,
 	return ret;
 }
 
+int repo_config_set_gently(struct repository *r,
+			   const char *key, const char *value)
+{
+	char *path = repo_git_path(r, "config");
+	int ret = git_config_set_multivar_in_file_gently(path, key, value, NULL, 0);
+	free(path);
+	return ret;
+}
+
+void repo_config_set(struct repository *r, const char *key, const char *value)
+{
+	if (!repo_config_set_gently(r, key, value))
+		return;
+	if (value)
+		die(_("could not set '%s' to '%s'"), key, value);
+	else
+		die(_("could not unset '%s'"), key);
+}
+
+int repo_config_set_worktree_gently(struct repository *r,
+				    const char *key, const char *value)
+{
+	char *path;
+	int ret;
+
+	path = get_worktree_config(r);
+	if (!path)
+		return CONFIG_INVALID_FILE;
+	ret = git_config_set_multivar_in_file_gently(path, key, value, NULL, 0);
+	free(path);
+	return ret;
+}
+
 /* Functions used historically to read configuration from 'the_repository' */
 void git_config(config_fn_t fn, void *data)
 {
@@ -2912,7 +2946,12 @@ int git_config_set_multivar_in_file_gently(const char *config_filename,
 
 	ret = 0;
 
-	/* Invalidate the config cache */
+	/*
+	 * Invalidate the config cache
+	 *
+	 * NEEDSWORK: invalidate _all_ existing config caches, not
+	 * just one from the_repository
+	 */
 	git_config_clear();
 
 out_free:
diff --git a/config.h b/config.h
index ee5d3fa7b4..62204dc252 100644
--- a/config.h
+++ b/config.h
@@ -103,6 +103,9 @@ extern int git_config_color(char *, const char *, const char *);
 extern int git_config_set_in_file_gently(const char *, const char *, const char *);
 extern void git_config_set_in_file(const char *, const char *, const char *);
 extern int git_config_set_gently(const char *, const char *);
+extern int repo_config_set_gently(struct repository *, const char *, const char *);
+extern void repo_config_set(struct repository *, const char *, const char *);
+extern int repo_config_set_worktree_gently(struct repository *, const char *, const char *);
 extern void git_config_set(const char *, const char *);
 extern int git_config_parse_key(const char *, char **, int *);
 extern int git_config_key_is_valid(const char *key);
-- 
2.20.0.482.g66447595a7
Derrick Stolee· Apr 30, 2019, 16:40 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 3/6] config.c: add repo_config_set_worktree_gently()

On 12/27/2018 10:56 AM, Nguyễn Thái Ngọc Duy wrote:
Show 14 quoted lines
> diff --git a/config.h b/config.h
> index ee5d3fa7b4..62204dc252 100644
> --- a/config.h
> +++ b/config.h
> @@ -103,6 +103,9 @@ extern int git_config_color(char *, const char *, const char *);
>  extern int git_config_set_in_file_gently(const char *, const char *, const char *);
>  extern void git_config_set_in_file(const char *, const char *, const char *);
>  extern int git_config_set_gently(const char *, const char *);
> +extern int repo_config_set_gently(struct repository *, const char *, const char *);
> +extern void repo_config_set(struct repository *, const char *, const char *);
> +extern int repo_config_set_worktree_gently(struct repository *, const char *, const char *);
>  extern void git_config_set(const char *, const char *);
>  extern int git_config_parse_key(const char *, char **, int *);
>  extern int git_config_key_is_valid(const char *key);

I know this is an old thread, but the patch is still in 'pu'. These methods do not appear to have any callers. Perhaps this patch should be dropped until there is a reason to include it?

Thanks, -Stolee

Duy Nguyen· May 1, 2019, 09:55 UTC · re: Derrick Stolee · lore

Re: [PATCH 3/6] config.c: add repo_config_set_worktree_gently()

On Tue, Apr 30, 2019 at 11:40 PM Derrick Stolee <stolee@gmail.com> wrote:
Show 20 quoted lines
>
> On 12/27/2018 10:56 AM, Nguyễn Thái Ngọc Duy wrote:
> > diff --git a/config.h b/config.h
> > index ee5d3fa7b4..62204dc252 100644
> > --- a/config.h
> > +++ b/config.h
> > @@ -103,6 +103,9 @@ extern int git_config_color(char *, const char *, const char *);
> >  extern int git_config_set_in_file_gently(const char *, const char *, const char *);
> >  extern void git_config_set_in_file(const char *, const char *, const char *);
> >  extern int git_config_set_gently(const char *, const char *);
> > +extern int repo_config_set_gently(struct repository *, const char *, const char *);
> > +extern void repo_config_set(struct repository *, const char *, const char *);
> > +extern int repo_config_set_worktree_gently(struct repository *, const char *, const char *);
> >  extern void git_config_set(const char *, const char *);
> >  extern int git_config_parse_key(const char *, char **, int *);
> >  extern int git_config_key_is_valid(const char *key);
>
> I know this is an old thread, but the patch is still in 'pu'. These methods
> do not appear to have any callers. Perhaps this patch should be dropped
> until there is a reason to include it?

That series was broken down to test the water before the whole sumodule support on multiple worktree series enters. But since this series is not going anyway, don't worry I don't think it will ever enter 'master'.

> Thanks,
> -Stolee
-- 
Duy
Nguyễn Thái Ngọc Duy· Dec 27, 2018, 15:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 4/6] config: use OPT_FILENAME()

Do not handle prefix directly. It's simpler to use OPT_FILENAME() instead. The othe reason for doing this is because this code (where the deleted code is) will be factored out and called when "prefix" is not available.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/config.c | 6 +-----
 1 file changed, 1 insertion(+), 5 deletions(-)
Show changes to builtin/config.c +1 −5
diff --git a/builtin/config.c b/builtin/config.c
index 771cfa54bd..c22d25de12 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -126,7 +126,7 @@ static struct option builtin_config_options[] = {
 	OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
 	OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
 	OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")),
-	OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
+	OPT_FILENAME('f', "file", &given_config_source.file, N_("use given config file")),
 	OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
 	OPT_GROUP(N_("Action")),
 	OPT_BIT(0, "get", &actions, N_("get value: name [value-regex]"), ACTION_GET),
@@ -657,10 +657,6 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 			      "extension worktreeConfig is enabled. "
 			      "Please read \"CONFIGURATION FILE\"\n"
 			      "section in \"git help worktree\" for details"));
-	} else if (given_config_source.file) {
-		if (!is_absolute_path(given_config_source.file) && prefix)
-			given_config_source.file =
-				prefix_filename(prefix, given_config_source.file);
 	}
 
 	if (respect_includes_opt == -1)
-- 
2.20.0.482.g66447595a7
Eric Sunshine· Dec 27, 2018, 20:40 UTC · re: Nguyễn Thái Ngọc Duy · lore

Re: [PATCH 4/6] config: use OPT_FILENAME()

On Thu, Dec 27, 2018 at 10:56 AM Nguyễn Thái Ngọc Duy <pclouds@gmail.com> wrote:
> Do not handle prefix directly. It's simpler to use OPT_FILENAME()
> instead. The othe reason for doing this is because this code (where
s/othe/other/
> the deleted code is) will be factored out and called when "prefix" is
> not available.
>
> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
Nguyễn Thái Ngọc Duy· Dec 27, 2018, 15:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 5/6] config: factor out set_config_source_file()

In the next patch, "config" is taught to move some config variables from one file to another. We need to specify two locations, source and destination, and the plan is reusing the same --global/--local/... option. Factor this code out for reuse later.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 builtin/config.c | 113 +++++++++++++++++++++++++----------------------
 1 file changed, 59 insertions(+), 54 deletions(-)
Show changes to builtin/config.c +59 −54
diff --git a/builtin/config.c b/builtin/config.c
index c22d25de12..61a6a5a0e1 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -71,6 +71,64 @@ static int show_origin;
 
 static NORETURN void usage_builtin_config(void);
 
+static void set_config_source_file(void)
+{
+	int nongit = !startup_info->have_repository;
+
+	if (use_global_config + use_system_config + use_local_config +
+	    use_worktree_config +
+	    !!given_config_source.file + !!given_config_source.blob > 1)
+		die(_("only one config file at a time"));
+
+	if (use_local_config && nongit)
+		die(_("--local can only be used inside a git repository"));
+
+	if (given_config_source.blob && nongit)
+		die(_("--blob can only be used inside a git repository"));
+
+	if (given_config_source.file &&
+			!strcmp(given_config_source.file, "-")) {
+		given_config_source.file = NULL;
+		given_config_source.use_stdin = 1;
+	}
+
+	if (use_global_config) {
+		char *user_config = expand_user_path("~/.gitconfig", 0);
+		char *xdg_config = xdg_config_home("config");
+
+		if (!user_config)
+			/*
+			 * It is unknown if HOME/.gitconfig exists, so
+			 * we do not know if we should write to XDG
+			 * location; error out even if XDG_CONFIG_HOME
+			 * is set and points at a sane location.
+			 */
+			die(_("$HOME not set"));
+
+		if (access_or_warn(user_config, R_OK, 0) &&
+		    xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
+			given_config_source.file = xdg_config;
+			free(user_config);
+		} else {
+			given_config_source.file = user_config;
+			free(xdg_config);
+		}
+	}
+	else if (use_system_config)
+		given_config_source.file = git_etc_gitconfig();
+	else if (use_local_config)
+		given_config_source.file = git_pathdup("config");
+	else if (use_worktree_config) {
+		given_config_source.file = get_worktree_config(the_repository);
+		if (!given_config_source.file)
+			die(_("--worktree cannot be used with multiple "
+			      "working trees unless the config\n"
+			      "extension worktreeConfig is enabled. "
+			      "Please read \"CONFIGURATION FILE\"\n"
+			      "section in \"git help worktree\" for details"));
+	}
+}
+
 static int option_parse_type(const struct option *opt, const char *arg,
 			     int unset)
 {
@@ -604,60 +662,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 			     builtin_config_usage,
 			     PARSE_OPT_STOP_AT_NON_OPTION);
 
-	if (use_global_config + use_system_config + use_local_config +
-	    use_worktree_config +
-	    !!given_config_source.file + !!given_config_source.blob > 1) {
-		error(_("only one config file at a time"));
-		usage_builtin_config();
-	}
-
-	if (use_local_config && nongit)
-		die(_("--local can only be used inside a git repository"));
-
-	if (given_config_source.blob && nongit)
-		die(_("--blob can only be used inside a git repository"));
-
-	if (given_config_source.file &&
-			!strcmp(given_config_source.file, "-")) {
-		given_config_source.file = NULL;
-		given_config_source.use_stdin = 1;
-	}
-
-	if (use_global_config) {
-		char *user_config = expand_user_path("~/.gitconfig", 0);
-		char *xdg_config = xdg_config_home("config");
-
-		if (!user_config)
-			/*
-			 * It is unknown if HOME/.gitconfig exists, so
-			 * we do not know if we should write to XDG
-			 * location; error out even if XDG_CONFIG_HOME
-			 * is set and points at a sane location.
-			 */
-			die(_("$HOME not set"));
-
-		if (access_or_warn(user_config, R_OK, 0) &&
-		    xdg_config && !access_or_warn(xdg_config, R_OK, 0)) {
-			given_config_source.file = xdg_config;
-			free(user_config);
-		} else {
-			given_config_source.file = user_config;
-			free(xdg_config);
-		}
-	}
-	else if (use_system_config)
-		given_config_source.file = git_etc_gitconfig();
-	else if (use_local_config)
-		given_config_source.file = git_pathdup("config");
-	else if (use_worktree_config) {
-		given_config_source.file = get_worktree_config(the_repository);
-		if (!given_config_source.file)
-			die(_("--worktree cannot be used with multiple "
-			      "working trees unless the config\n"
-			      "extension worktreeConfig is enabled. "
-			      "Please read \"CONFIGURATION FILE\"\n"
-			      "section in \"git help worktree\" for details"));
-	}
+	set_config_source_file();
 
 	if (respect_includes_opt == -1)
 		config_options.respect_includes = !given_config_source.file;
-- 
2.20.0.482.g66447595a7
Nguyễn Thái Ngọc Duy· Dec 27, 2018, 15:56 UTC · re: Nguyễn Thái Ngọc Duy · lore

[PATCH 6/6] config: add --move-to

This option can be used to move one or multiple variables from one place to another, e.g. to move some aliases from repo config to user config, or from repo config to per-worktree config.

This will be useful for moving config variables around when extensions.worktreeConfig is enabled. E.g.

    git config --local --move-to --worktree core.worktree
    git config --local --move-glob-to --worktree 'submodule.*.*'

The implementation is definitely not the best. We could for example lock both source and destination files before doing any update, and perhaps edit these files just once instead of once per key.

But it adds a lot more complication to config update code. Let's stay with something simple for now. It's not worse than scripting using "git config". Optimization could be done later.

Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
 Documentation/git-config.txt   |  12 ++++
 Documentation/git-worktree.txt |  16 +++--
 builtin/config.c               | 120 +++++++++++++++++++++++++++++++++
 t/t1300-config.sh              |  54 +++++++++++++++
 4 files changed, 196 insertions(+), 6 deletions(-)
Show changes to 4 files +196 −6

Documentation/git-config.txt, Documentation/git-worktree.txt, builtin/config.c, t/t1300-config.sh

diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt
index 1bfe9f56a7..495bb57416 100644
--- a/Documentation/git-config.txt
+++ b/Documentation/git-config.txt
@@ -20,6 +20,9 @@ SYNOPSIS
 'git config' [<file-option>] --unset-all name [value_regex]
 'git config' [<file-option>] --rename-section old_name new_name
 'git config' [<file-option>] --remove-section name
+'git config' [<file-option>] --move-to name
+'git config' [<file-option>] --move-regexp-to name-regexp
+'git config' [<file-option>] --move-glob-to name-glob
 'git config' [<file-option>] [--show-origin] [-z|--null] [--name-only] -l | --list
 'git config' [<file-option>] --get-color name [default]
 'git config' [<file-option>] --get-colorbool name [stdout-is-tty]
@@ -161,6 +164,15 @@ See also <<FILES>>.
 --unset-all::
 	Remove all lines matching the key from config file.
 
+--move-to::
+--move-regexp-to::
+--move-glob-to::
+	Move a config variable (or multiple variables matching the
+	given regular expression or glob pattern) to a new file. Any
+	option about the config file location after `--move-to` or
+	`--move-to-regexp` specifies the move destination. Existing
+	config of the same name remains.
+
 -l::
 --list::
 	List all variables set in config file, along with their values.
diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt
index cb86318f3e..aae8e1d8b2 100644
--- a/Documentation/git-worktree.txt
+++ b/Documentation/git-worktree.txt
@@ -252,13 +252,17 @@ rev-parse --git-path config.worktree`. You can add or update
 configuration in this file with `git config --worktree`. Older Git
 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 you have them in $GIT_DIR/config before, 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:
+Note that in this file, the exception for `core.bare` and
+`core.worktree` is gone. If you have them in $GIT_DIR/config before,
+you must move them to the `config.worktree` of the main working tree.
 
- - `core.worktree` and `core.bare` should never be shared
+------------
+$ git config --local --move-to --worktree core.bare
+$ git config --local --move-to --worktree core.worktree
+------------
+
+You may also take this opportunity to review and move other
+configuration that you do not want to share to all working trees:
 
  - `core.sparseCheckout` is recommended per working tree, unless you
    are sure you always use sparse checkout for all working trees.
diff --git a/builtin/config.c b/builtin/config.c
index 61a6a5a0e1..20266824f3 100644
--- a/builtin/config.c
+++ b/builtin/config.c
@@ -26,6 +26,7 @@ static char term = '\n';
 
 static int use_global_config, use_system_config, use_local_config;
 static int use_worktree_config;
+static struct git_config_source move_source;
 static struct git_config_source given_config_source;
 static int actions, type;
 static char *default_value;
@@ -50,6 +51,9 @@ static int show_origin;
 #define ACTION_GET_COLOR (1<<13)
 #define ACTION_GET_COLORBOOL (1<<14)
 #define ACTION_GET_URLMATCH (1<<15)
+#define ACTION_MOVE (1<<16)
+#define ACTION_MOVE_REGEXP (1<<17)
+#define ACTION_MOVE_GLOB (1<<18)
 
 /*
  * The actions "ACTION_LIST | ACTION_GET_*" which may produce more than
@@ -178,6 +182,25 @@ static int option_parse_type(const struct option *opt, const char *arg,
 	return 0;
 }
 
+static int option_move_cb(const struct option *opt,
+			  const char *arg, int unset)
+{
+	BUG_ON_OPT_NEG(unset);
+	BUG_ON_OPT_ARG(arg);
+
+	set_config_source_file();
+	memcpy(&move_source, &given_config_source, sizeof(move_source));
+
+	memset(&given_config_source, 0, sizeof(given_config_source));
+	use_global_config = 0;
+	use_system_config = 0;
+	use_local_config = 0;
+	use_worktree_config = 0;
+
+	actions = opt->defval;
+	return 0;
+}
+
 static struct option builtin_config_options[] = {
 	OPT_GROUP(N_("Config file location")),
 	OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
@@ -197,6 +220,18 @@ static struct option builtin_config_options[] = {
 	OPT_BIT(0, "unset-all", &actions, N_("remove all matches: name [value-regex]"), ACTION_UNSET_ALL),
 	OPT_BIT(0, "rename-section", &actions, N_("rename section: old-name new-name"), ACTION_RENAME_SECTION),
 	OPT_BIT(0, "remove-section", &actions, N_("remove a section: name"), ACTION_REMOVE_SECTION),
+	{ OPTION_CALLBACK, 0, "move-to", NULL, NULL,
+	  N_("move a variable to a different config file"),
+	  PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+	  option_move_cb, ACTION_MOVE },
+	{ OPTION_CALLBACK, 0, "move-regexp-to", NULL, NULL,
+	  N_("move matching variables to a different config file"),
+	  PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+	  option_move_cb, ACTION_MOVE_REGEXP },
+	{ OPTION_CALLBACK, 0, "move-glob-to", NULL, NULL,
+	  N_("move matching variables to a different config file"),
+	  PARSE_OPT_NONEG | PARSE_OPT_NOARG,
+	  option_move_cb, ACTION_MOVE_GLOB },
 	OPT_BIT('l', "list", &actions, N_("list all"), ACTION_LIST),
 	OPT_BIT('e', "edit", &actions, N_("open an editor"), ACTION_EDIT),
 	OPT_BIT(0, "get-color", &actions, N_("find the color configured: slot [default]"), ACTION_GET_COLOR),
@@ -426,6 +461,84 @@ static int get_value(const char *key_, const char *regex_)
 	return ret;
 }
 
+struct move_config_cb {
+	struct string_list keys;
+	const char *key;
+	regex_t key_re;
+};
+
+static int collect_move_config(const char *key, const char *value, void *cb)
+{
+	struct move_config_cb *data = cb;
+
+	switch (actions) {
+	case ACTION_MOVE:
+		if (strcasecmp(data->key, key))
+			return 0;
+		break;
+	case ACTION_MOVE_REGEXP:
+		if (regexec(&data->key_re, key, 0, NULL, 0))
+			return 0;
+		break;
+	case ACTION_MOVE_GLOB:
+		if (wildmatch(data->key, key, WM_CASEFOLD))
+			return 0;
+		break;
+	default:
+		BUG("action %d cannot get here", actions);
+	}
+
+	string_list_append(&data->keys, key)->util = xstrdup(value);
+	return 0;
+}
+
+static int move_config(const char *key)
+{
+	struct move_config_cb cb;
+	int i, ret = 0;
+
+	config_options.respect_includes = 0;
+	if (!move_source.file && !move_source.use_stdin && !move_source.blob)
+		die(_("unknown config source"));
+
+	string_list_init(&cb.keys, 1);
+	cb.key = key;
+	if (actions == ACTION_MOVE_REGEXP &&
+	    regcomp(&cb.key_re, key, REG_EXTENDED | REG_ICASE))
+		die(_("invalid key pattern: %s"), key);
+
+	config_with_options(collect_move_config, &cb,
+			    &move_source, &config_options);
+
+	for (i = 0; i < cb.keys.nr && !ret; i++) {
+		const char *key = cb.keys.items[i].string;
+		const char *value = cb.keys.items[i].util;
+		const char *dest = given_config_source.file;
+
+		ret = git_config_set_multivar_in_file_gently(
+			dest, key, value, CONFIG_REGEX_NONE, 0);
+	}
+
+	/*
+	 * OK all keys have been copied successfully, time to delete
+	 * old ones
+	 */
+	if (!ret && move_source.file) {
+		for (i = 0; i < cb.keys.nr; i++) {
+			const char *key = cb.keys.items[i].string;
+			const char *src = move_source.file;
+
+			git_config_set_multivar_in_file_gently(
+				src, key, NULL, NULL, 1);
+		}
+	}
+
+	string_list_clear(&cb.keys, 1);
+	if (actions == ACTION_MOVE_REGEXP)
+		regfree(&cb.key_re);
+	return ret;
+}
+
 static char *normalize_value(const char *key, const char *value)
 {
 	if (!value)
@@ -862,6 +975,13 @@ int cmd_config(int argc, const char **argv, const char *prefix)
 			color_stdout_is_tty = git_config_bool("command line", argv[1]);
 		return get_colorbool(argv[0], argc == 2);
 	}
+	else if (actions == ACTION_MOVE ||
+		 actions == ACTION_MOVE_REGEXP ||
+		 actions == ACTION_MOVE_GLOB) {
+		check_write();
+		check_argc(argc, 1, 1);
+		return move_config(argv[0]);
+	}
 
 	return 0;
 }
diff --git a/t/t1300-config.sh b/t/t1300-config.sh
index 9652b241c7..6969e6092b 100755
--- a/t/t1300-config.sh
+++ b/t/t1300-config.sh
@@ -1844,4 +1844,58 @@ test_expect_success '--replace-all does not invent newlines' '
 	test_cmp expect .git/config
 '
 
+test_expect_success '--move-to moves keys' '
+	test_when_finished rm -f dest &&
+	git config single.foo bar &&
+	git config multi.foo bar1 &&
+	git config --add multi.foo bar2 &&
+	git config --local --move-to -f dest SINGLE.foo &&
+	! git config single.foo &&
+	test_cmp_config bar -f dest single.foo &&
+	git config --local --move-to -f dest multi.FOO &&
+	! git config multi.foo &&
+	git config -f dest --get-all multi.foo | sort >actual &&
+	cat >expected <<-\EOF &&
+	bar1
+	bar2
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success '--move-regexp-to moves keys' '
+	test_when_finished rm -f dest &&
+	git config single.foo bar &&
+	git config multi.foo bar1 &&
+	git config --add multi.foo bar2 &&
+	git config --local --move-regexp-to -f dest "S.*OO" &&
+	! git config single.foo &&
+	test_cmp_config bar -f dest single.foo &&
+	git config --local --move-regexp-to -f dest ^multi &&
+	! git config multi.foo &&
+	git config -f dest --get-all multi.foo | sort >actual &&
+	cat >expected <<-\EOF &&
+	bar1
+	bar2
+	EOF
+	test_cmp expected actual
+'
+
+test_expect_success '--move-glob-to moves keys' '
+	test_when_finished rm -f dest &&
+	git config single.foo bar &&
+	git config multi.foo bar1 &&
+	git config --add multi.foo bar2 &&
+	git config --local --move-glob-to -f dest "SINGLE.*" &&
+	! git config single.foo &&
+	test_cmp_config bar -f dest single.foo &&
+	git config --local --move-glob-to -f dest "m*.foo" &&
+	! git config multi.foo &&
+	git config -f dest --get-all multi.foo | sort >actual &&
+	cat >expected <<-\EOF &&
+	bar1
+	bar2
+	EOF
+	test_cmp expected actual
+'
+
 test_done
-- 
2.20.0.482.g66447595a7

← back to recent threads