diff options
-rw-r--r-- | Documentation/config.txt | 12 | ||||
-rw-r--r-- | Documentation/git-config.txt | 26 | ||||
-rw-r--r-- | Documentation/git-worktree.txt | 33 | ||||
-rw-r--r-- | Documentation/gitrepository-layout.txt | 8 | ||||
-rw-r--r-- | builtin/config.c | 19 | ||||
-rw-r--r-- | cache.h | 2 | ||||
-rw-r--r-- | config.c | 11 | ||||
-rw-r--r-- | environment.c | 1 | ||||
-rw-r--r-- | setup.c | 40 | ||||
-rwxr-xr-x | t/t1300-config.sh | 79 | ||||
-rwxr-xr-x | t/t2029-worktree-config.sh | 79 | ||||
-rw-r--r-- | t/test-lib-functions.sh | 23 |
12 files changed, 255 insertions, 78 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 75b8f6964d..3e735f1a9a 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 optionally +`config.worktree` (see `extensions.worktreeConfig` below) in each +repository are 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. @@ -291,6 +292,13 @@ include::config/advice.txt[] include::config/core.txt[] +extensions.worktreeConfig:: + If set, by default "git config" reads from both "config" and + "config.worktree" file from GIT_DIR in that order. In + multiple working directory mode, "config" file is shared while + "config.worktree" is per-working directory (i.e., it's in + GIT_COMMON_DIR/worktrees/<id>/config.worktree) + include::config/add.txt[] include::config/alias.txt[] diff --git a/Documentation/git-config.txt b/Documentation/git-config.txt index 5e87d82933..1bfe9f56a7 100644 --- a/Documentation/git-config.txt +++ b/Documentation/git-config.txt @@ -45,13 +45,15 @@ unset an existing `--type` specifier with `--no-type`. When reading, the values are read from the system, global and repository local configuration files by default, and options -`--system`, `--global`, `--local` and `--file <filename>` can be -used to tell the command to read from only that location (see <<FILES>>). +`--system`, `--global`, `--local`, `--worktree` and +`--file <filename>` can be used to tell the command to read from only +that location (see <<FILES>>). When writing, the new value is written to the repository local configuration file by default, and options `--system`, `--global`, -`--file <filename>` can be used to tell the command to write to -that location (you can say `--local` but that is the default). +`--worktree`, `--file <filename>` can be used to tell the command to +write to that location (you can say `--local` but that is the +default). This command will fail with non-zero status upon error. Some exit codes are: @@ -131,6 +133,11 @@ from all available files. + See also <<FILES>>. +--worktree:: + Similar to `--local` except that `.git/config.worktree` is + read from or written to if `extensions.worktreeConfig` is + present. If not it's the same as `--local`. + -f config-file:: --file config-file:: Use the given config file instead of the one specified by GIT_CONFIG. @@ -281,6 +288,10 @@ $XDG_CONFIG_HOME/git/config:: $GIT_DIR/config:: Repository specific configuration file. +$GIT_DIR/config.worktree:: + This is optional and is only searched when + `extensions.worktreeConfig` is present in $GIT_DIR/config. + If no further options are given, all reading options will read all of these files that are available. If the global or the system-wide configuration file are not available they will be ignored. If the repository configuration @@ -299,9 +310,10 @@ configuration file. Note that this also affects options like `--replace-all` and `--unset`. *'git config' will only ever change one file at a time*. You can override these rules either by command-line options or by environment -variables. The `--global` and the `--system` options will limit the file used -to the global or system-wide file respectively. The `GIT_CONFIG` environment -variable has a similar effect, but you can specify any filename you want. +variables. The `--global`, `--system` and `--worktree` options will limit +the file used to the global, system-wide or per-worktree file respectively. +The `GIT_CONFIG` environment variable has a similar effect, but you +can specify any filename you want. ENVIRONMENT diff --git a/Documentation/git-worktree.txt b/Documentation/git-worktree.txt index 73520434f6..5e986ce8aa 100644 --- a/Documentation/git-worktree.txt +++ b/Documentation/git-worktree.txt @@ -204,6 +204,36 @@ working trees, it can be used to identify worktrees. For example if you only have two working trees, at "/abc/def/ghi" and "/abc/def/ggg", then "ghi" or "def/ghi" is enough to point to the former working tree. +CONFIGURATION FILE +------------------ +By default, the repository "config" file is shared across all working +trees. If the config variables `core.bare` or `core.worktree` are +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 "worktreeConfig" extension, e.g.: + +------------ +$ git config extensions.worktreeConfig true +------------ + +In this mode, specific configuration stays in the path pointed by `git +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: + + - `core.worktree` and `core.bare` should never be shared + + - `core.sparseCheckout` is recommended per working tree, unless you + are sure you always use sparse checkout for all working trees. + DETAILS ------- Each linked working tree has a private sub-directory in the repository's @@ -253,6 +283,9 @@ to `/path/main/.git/worktrees/test-next` then a file named `test-next` entry from being pruned. See linkgit:gitrepository-layout[5] for details. +When extensions.worktreeConfig is enabled, the config file +`.git/worktrees/<id>/config.worktree` is read after `.git/config` is. + LIST OUTPUT FORMAT ------------------ The worktree list command has two output formats. The default format shows the diff --git a/Documentation/gitrepository-layout.txt b/Documentation/gitrepository-layout.txt index e85148f05e..36fcca8087 100644 --- a/Documentation/gitrepository-layout.txt +++ b/Documentation/gitrepository-layout.txt @@ -143,6 +143,11 @@ config:: if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be used instead. +config.worktree:: + Working directory specific configuration file for the main + working directory in multiple working directory setup (see + linkgit:git-worktree[1]). + branches:: A slightly deprecated way to store shorthands to be used to specify a URL to 'git fetch', 'git pull' and 'git push'. @@ -275,6 +280,9 @@ worktrees/<id>/locked:: or manually by `git worktree prune`. The file may contain a string explaining why the repository is locked. +worktrees/<id>/config.worktree:: + Working directory specific configuration file. + SEE ALSO -------- linkgit:git-init[1], diff --git a/builtin/config.c b/builtin/config.c index 97b58c4aea..84385ef165 100644 --- a/builtin/config.c +++ b/builtin/config.c @@ -5,6 +5,7 @@ #include "parse-options.h" #include "urlmatch.h" #include "quote.h" +#include "worktree.h" static const char *const builtin_config_usage[] = { N_("git config [<options>]"), @@ -24,6 +25,7 @@ static char key_delim = ' '; 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 given_config_source; static int actions, type; static char *default_value; @@ -123,6 +125,7 @@ static struct option builtin_config_options[] = { OPT_BOOL(0, "global", &use_global_config, N_("use global config file")), 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_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")), OPT_GROUP(N_("Action")), @@ -602,6 +605,7 @@ int cmd_config(int argc, const char **argv, const char *prefix) 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(); @@ -645,7 +649,20 @@ int cmd_config(int argc, const char **argv, const char *prefix) given_config_source.file = git_etc_gitconfig(); else if (use_local_config) given_config_source.file = git_pathdup("config"); - else if (given_config_source.file) { + 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]) + 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 = prefix_filename(prefix, given_config_source.file); @@ -962,11 +962,13 @@ extern int grafts_replace_parents; extern int repository_format_precious_objects; extern char *repository_format_partial_clone; extern const char *core_partial_clone_filter_default; +extern int repository_format_worktree_config; struct repository_format { int version; int precious_objects; char *partial_clone; /* value of extensions.partialclone */ + int worktree_config; int is_bare; int hash_algo; char *work_tree; @@ -1695,6 +1695,17 @@ static int do_git_config_sequence(const struct config_options *opts, if (repo_config && !access_or_die(repo_config, R_OK, 0)) ret += git_config_from_file(fn, repo_config, data); + /* + * 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); + } + current_parsing_scope = CONFIG_SCOPE_CMDLINE; if (git_config_from_parameters(fn, data) < 0) die(_("unable to parse command-line config")); diff --git a/environment.c b/environment.c index 3f3c8746c2..268310b3dc 100644 --- a/environment.c +++ b/environment.c @@ -33,6 +33,7 @@ int ref_paranoia = -1; int repository_format_precious_objects; char *repository_format_partial_clone; const char *core_partial_clone_filter_default; +int repository_format_worktree_config; const char *git_commit_encoding; const char *git_log_output_encoding; const char *apply_default_whitespace; @@ -402,6 +402,20 @@ void setup_work_tree(void) initialized = 1; } +static int read_worktree_config(const char *var, const char *value, void *vdata) +{ + struct repository_format *data = vdata; + + if (strcmp(var, "core.bare") == 0) { + data->is_bare = git_config_bool(var, value); + } else if (strcmp(var, "core.worktree") == 0) { + if (!value) + return config_error_nonbool(var); + data->work_tree = xstrdup(value); + } + return 0; +} + static int check_repo_format(const char *var, const char *value, void *vdata) { struct repository_format *data = vdata; @@ -423,16 +437,13 @@ static int check_repo_format(const char *var, const char *value, void *vdata) if (!value) return config_error_nonbool(var); data->partial_clone = xstrdup(value); - } else + } else if (!strcmp(ext, "worktreeconfig")) + data->worktree_config = git_config_bool(var, value); + else string_list_append(&data->unknown_extensions, ext); - } else if (strcmp(var, "core.bare") == 0) { - data->is_bare = git_config_bool(var, value); - } else if (strcmp(var, "core.worktree") == 0) { - if (!value) - return config_error_nonbool(var); - data->work_tree = xstrdup(value); } - return 0; + + return read_worktree_config(var, value, vdata); } static int check_repository_format_gently(const char *gitdir, struct repository_format *candidate, int *nongit_ok) @@ -466,7 +477,20 @@ static int check_repository_format_gently(const char *gitdir, struct repository_ repository_format_precious_objects = candidate->precious_objects; repository_format_partial_clone = candidate->partial_clone; + repository_format_worktree_config = candidate->worktree_config; string_list_clear(&candidate->unknown_extensions, 0); + + if (repository_format_worktree_config) { + /* + * pick up core.bare and core.worktree from per-worktree + * config if present + */ + strbuf_addf(&sb, "%s/config.worktree", gitdir); + git_config_from_file(read_worktree_config, sb.buf, candidate); + strbuf_release(&sb); + has_common = 0; + } + if (!has_common) { if (candidate->is_bare != -1) { is_bare_repository_cfg = candidate->is_bare; diff --git a/t/t1300-config.sh b/t/t1300-config.sh index e2cd50ecfc..9652b241c7 100755 --- a/t/t1300-config.sh +++ b/t/t1300-config.sh @@ -76,15 +76,11 @@ EOF test_expect_success 'non-match result' 'test_cmp expect .git/config' test_expect_success 'find mixed-case key by canonical name' ' - echo Second >expect && - git config cores.whatever >actual && - test_cmp expect actual + test_cmp_config Second cores.whatever ' test_expect_success 'find mixed-case key by non-canonical name' ' - echo Second >expect && - git config CoReS.WhAtEvEr >actual && - test_cmp expect actual + test_cmp_config Second CoReS.WhAtEvEr ' test_expect_success 'subsections are not canonicalized by git-config' ' @@ -94,12 +90,8 @@ test_expect_success 'subsections are not canonicalized by git-config' ' [section "SubSection"] key = two EOF - echo one >expect && - git config section.subsection.key >actual && - test_cmp expect actual && - echo two >expect && - git config section.SubSection.key >actual && - test_cmp expect actual + test_cmp_config one section.subsection.key && + test_cmp_config two section.SubSection.key ' cat > .git/config <<\EOF @@ -212,9 +204,7 @@ test_expect_success 'really really mean test' ' ' test_expect_success 'get value' ' - echo alpha >expect && - git config beta.haha >actual && - test_cmp expect actual + test_cmp_config alpha beta.haha ' cat > expect << EOF @@ -251,15 +241,11 @@ test_expect_success 'non-match' ' ' test_expect_success 'non-match value' ' - echo wow >expect && - git config --get nextsection.nonewline !for >actual && - test_cmp expect actual + test_cmp_config wow --get nextsection.nonewline !for ' test_expect_success 'multi-valued get returns final one' ' - echo "wow2 for me" >expect && - git config --get nextsection.nonewline >actual && - test_cmp expect actual + test_cmp_config "wow2 for me" --get nextsection.nonewline ' test_expect_success 'multi-valued get-all returns all' ' @@ -520,21 +506,11 @@ test_expect_success 'editing stdin is an error' ' test_expect_success 'refer config from subdirectory' ' mkdir x && - ( - cd x && - echo strasse >expect && - git config --get --file ../other-config ein.bahn >actual && - test_cmp expect actual - ) - + test_cmp_config -C x strasse --get --file ../other-config ein.bahn ' test_expect_success 'refer config from subdirectory via --file' ' - ( - cd x && - git config --file=../other-config --get ein.bahn >actual && - test_cmp expect actual - ) + test_cmp_config -C x strasse --file=../other-config --get ein.bahn ' cat > expect << EOF @@ -688,16 +664,13 @@ test_expect_success numbers ' test_expect_success '--int is at least 64 bits' ' git config giga.watts 121g && - echo 129922760704 >expect && - git config --int --get giga.watts >actual && - test_cmp expect actual + echo >expect && + test_cmp_config 129922760704 --int --get giga.watts ' test_expect_success 'invalid unit' ' git config aninvalid.unit "1auto" && - echo 1auto >expect && - git config aninvalid.unit >actual && - test_cmp expect actual && + test_cmp_config 1auto aninvalid.unit && test_must_fail git config --int --get aninvalid.unit 2>actual && test_i18ngrep "bad numeric config value .1auto. for .aninvalid.unit. in file .git/config: invalid unit" actual ' @@ -1039,9 +1012,7 @@ test_expect_success '--null --get-regexp' ' test_expect_success 'inner whitespace kept verbatim' ' git config section.val "foo bar" && - echo "foo bar" >expect && - git config section.val >actual && - test_cmp expect actual + test_cmp_config "foo bar" section.val ' test_expect_success SYMLINKS 'symlinked configuration' ' @@ -1809,21 +1780,15 @@ big = 1M EOF test_expect_success 'identical modern --type specifiers are allowed' ' - git config --type=int --type=int core.big >actual && - echo 1048576 >expect && - test_cmp expect actual + test_cmp_config 1048576 --type=int --type=int core.big ' test_expect_success 'identical legacy --type specifiers are allowed' ' - git config --int --int core.big >actual && - echo 1048576 >expect && - test_cmp expect actual + test_cmp_config 1048576 --int --int core.big ' test_expect_success 'identical mixed --type specifiers are allowed' ' - git config --int --type=int core.big >actual && - echo 1048576 >expect && - test_cmp expect actual + test_cmp_config 1048576 --int --type=int core.big ' test_expect_success 'non-identical modern --type specifiers are not allowed' ' @@ -1842,21 +1807,15 @@ test_expect_success 'non-identical mixed --type specifiers are not allowed' ' ' test_expect_success '--type allows valid type specifiers' ' - echo "true" >expect && - git config --type=bool core.foo >actual && - test_cmp expect actual + test_cmp_config true --type=bool core.foo ' test_expect_success '--no-type unsets type specifiers' ' - echo "10" >expect && - git config --type=bool --no-type core.number >actual && - test_cmp expect actual + test_cmp_config 10 --type=bool --no-type core.number ' test_expect_success 'unset type specifiers may be reset to conflicting ones' ' - echo 1048576 >expect && - git config --type=bool --no-type --type=int core.big >actual && - test_cmp expect actual + test_cmp_config 1048576 --type=bool --no-type --type=int core.big ' test_expect_success '--type rejects unknown specifiers' ' diff --git a/t/t2029-worktree-config.sh b/t/t2029-worktree-config.sh new file mode 100755 index 0000000000..286121d8de --- /dev/null +++ b/t/t2029-worktree-config.sh @@ -0,0 +1,79 @@ +#!/bin/sh + +test_description="config file in multi worktree" + +. ./test-lib.sh + +test_expect_success 'setup' ' + test_commit start +' + +test_expect_success 'config --worktree in single worktree' ' + git config --worktree foo.bar true && + test_cmp_config true foo.bar +' + +test_expect_success 'add worktrees' ' + git worktree add wt1 && + git worktree add wt2 +' + +test_expect_success 'config --worktree without extension' ' + test_must_fail git config --worktree foo.bar false +' + +test_expect_success 'enable worktreeConfig extension' ' + git config extensions.worktreeConfig true && + test_cmp_config true extensions.worktreeConfig +' + +test_expect_success 'config is shared as before' ' + git config this.is shared && + test_cmp_config shared this.is && + test_cmp_config -C wt1 shared this.is && + test_cmp_config -C wt2 shared this.is +' + +test_expect_success 'config is shared (set from another worktree)' ' + git -C wt1 config that.is also-shared && + test_cmp_config also-shared that.is && + test_cmp_config -C wt1 also-shared that.is && + test_cmp_config -C wt2 also-shared that.is +' + +test_expect_success 'config private to main worktree' ' + git config --worktree this.is for-main && + test_cmp_config for-main this.is && + test_cmp_config -C wt1 shared this.is && + test_cmp_config -C wt2 shared this.is +' + +test_expect_success 'config private to linked worktree' ' + git -C wt1 config --worktree this.is for-wt1 && + test_cmp_config for-main this.is && + test_cmp_config -C wt1 for-wt1 this.is && + test_cmp_config -C wt2 shared this.is +' + +test_expect_success 'core.bare no longer for main only' ' + test_config core.bare true && + test "$(git rev-parse --is-bare-repository)" = true && + test "$(git -C wt1 rev-parse --is-bare-repository)" = true && + test "$(git -C wt2 rev-parse --is-bare-repository)" = true +' + +test_expect_success 'per-worktree core.bare is picked up' ' + git -C wt1 config --worktree core.bare true && + test "$(git rev-parse --is-bare-repository)" = false && + test "$(git -C wt1 rev-parse --is-bare-repository)" = true && + test "$(git -C wt2 rev-parse --is-bare-repository)" = false +' + +test_expect_success 'config.worktree no longer read without extension' ' + git config --unset extensions.worktreeConfig && + test_cmp_config shared this.is && + test_cmp_config -C wt1 shared this.is && + test_cmp_config -C wt2 shared this.is +' + +test_done diff --git a/t/test-lib-functions.sh b/t/test-lib-functions.sh index 78d8c3783b..d158c8d0bf 100644 --- a/t/test-lib-functions.sh +++ b/t/test-lib-functions.sh @@ -747,6 +747,29 @@ test_cmp() { $GIT_TEST_CMP "$@" } +# Check that the given config key has the expected value. +# +# test_cmp_config [-C <dir>] <expected-value> +# [<git-config-options>...] <config-key> +# +# for example to check that the value of core.bar is foo +# +# test_cmp_config foo core.bar +# +test_cmp_config() { + local GD && + if test "$1" = "-C" + then + shift && + GD="-C $1" && + shift + fi && + printf "%s\n" "$1" >expect.config && + shift && + git $GD config "$@" >actual.config && + test_cmp expect.config actual.config +} + # test_cmp_bin - helper to compare binary files test_cmp_bin() { |