Commit 790bacd8 authored by Duy Nguyen's avatar Duy Nguyen

config: add --move-to[-regexp]

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, especially when
enabling or disabling extensions.worktreeConfig (see the update in
git-worktree.txt for a real example).

The implementation is definitely not the best. We could lock both
source and destination before the move, and perhaps edit these files
just once instead of once per key. But it adds a lot more complication
to config write code. Let's stay with something simple. We can
optimize it later.
parent 663c797b
......@@ -20,6 +20,8 @@ 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 (--local|--worktree|--global|...) name
'git config' [<file-option>] --move-to-regexp (--local|--worktree|--global|...) name-regexp
'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 +163,13 @@ See also <<FILES>>.
Remove all lines matching the key from config file.
Move a config variable (or multiple variables matching the
given regular expression) to a new file. Any option about the
config file location after `--move-to` or `--move-to-regexp`
specifies the move destination.
List all variables set in config file, along with their values.
......@@ -255,19 +255,39 @@ 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:
take this opportunity to review with `git config --list --local`
and move other configuration that you do not want to share to all
working trees:
- `core.worktree` and `core.bare` should never be shared
$ git config --local --move-to --worktree core.worktree
$ git config --local --move-to --worktree core.bare
- `core.sparseCheckout` is recommended per working tree, unless you
are sure you always use sparse checkout for all working trees.
$ git config --local --move-to --worktree core.sparseCheckout
- Most configuration variables under `submodule` group in superproject
should not be shared.
$ git config --local --move-to --worktree
$ git config --local --move-to-regexp --worktree 'submodule\..*\..*'
- If you use submodules, you may need to move `core.worktree` in each
submodule as well
$ git submodule foreach git config extensions.worktreeConfig true
$ git submodule foreach git config --local --move-to --worktree core.worktree
......@@ -2,6 +2,7 @@
#include "cache.h"
#include "config.h"
#include "color.h"
#include "dir.h"
#include "parse-options.h"
#include "urlmatch.h"
#include "quote.h"
......@@ -26,6 +27,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 +52,8 @@ 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)
* 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)
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,14 @@ 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"),
option_move_cb, ACTION_MOVE },
{ OPTION_CALLBACK, 0, "move-to-regexp", NULL, NULL,
N_("move matching variables to a different config file"),
option_move_cb, ACTION_MOVE_REGEXP },
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),
......@@ -427,6 +458,77 @@ static int get_value(const char *key_, const char *regex_)
return ret;
struct move_config_cb {
struct string_list keys;
int is_regexp;
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;
if (!data->is_regexp && strcasecmp(data->key, key))
return 0;
if (data->is_regexp && regexec(&data->key_re, key, 0, NULL, 0))
return 0;
string_list_append(&data->keys, key)->util = xstrdup(value);
return 0;
static int move_config(const char *key, int is_regexp)
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"));
if (move_source.file && given_config_source.file &&
!fspathcmp(move_source.file, given_config_source.file))
die(_("source and destination are the same: %s"),
string_list_init(&cb.keys, 1);
cb.key = key;
cb.is_regexp = is_regexp;
if (is_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 < && !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 <; i++) {
const char *key = cb.keys.items[i].string;
const char *src = move_source.file;
git_config_set_in_file_gently(src, key, NULL);
string_list_clear(&cb.keys, 1);
if (is_regexp)
return ret;
static char *normalize_value(const char *key, const char *value)
if (!value)
......@@ -863,6 +965,16 @@ 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) {
check_argc(argc, 1, 1);
return move_config(argv[0], 0);
else if (actions == ACTION_MOVE_REGEXP) {
check_argc(argc, 1, 1);
return move_config(argv[0], 1);
return 0;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment