worktree: add per-worktree config files

A new repo extension is added, worktreeConfig. When it is present: - Repository config reading by default includes $GIT_DIR/config _and_ $GIT_DIR/config.worktree. "config" file remains shared in multiple worktree setup. - The special treatment for core.bare and core.worktree, to stay effective only in main worktree, is gone. These config settings are supposed to be in config.worktree. This extension is most useful in multiple worktree setup because you now have an option to store per-worktree config (which is either .git/config.worktree for main worktree, or .git/worktrees/xx/config.worktree for linked ones). This extension can be used in single worktree mode, even though it's pretty much useless (but this can happen after you remove all linked worktrees and move back to single worktree). "git config" reads from both "config" and "config.worktree" by default (i.e. without either --user, --file...) when this extension is present. Default writes still go to "config", not "config.worktree". A new option --worktree is added for that (*). Since a new repo extension is introduced, existing git binaries should refuse to access to the repo (both from main and linked worktrees). So they will not misread the config file (i.e. skip the config.worktree part). They may still accidentally write to the config file anyway if they use with "git config --file <path>". This design places a bet on the assumption that the majority of config variables are shared so it is the default mode. A safer move would be default writes go to per-worktree file, so that accidental changes are isolated. (*) "git config --worktree" points back to "config" file when this extension is not present and there is only one worktree so that it works in any both single and multiple worktree setups. Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Oct 21, 2018 at 16:02 UTC 58b284a2e9123588eedc8c5ee17e8b069d9454f8
10 files changed +213 -18
Documentation/config.txt
+10 -2
@@ -2,8 +2,9 @@ CONFIGURATION FILE
2 ------------------
3
4 The Git configuration file contains a number of variables that affect
5 -the Git commands' behavior. The `.git/config` file in each repository
6 -is used to store the configuration for that repository, and
5 +the Git commands' behavior. The files `.git/config` and optionally
6 +`config.worktree` (see `extensions.worktreeConfig` below) in each
7 +repository are used to store the configuration for that repository, and
8 `$HOME/.gitconfig` is used to store a per-user configuration as
9 fallback values for the `.git/config` file. The file `/etc/gitconfig`
10 can be used to store a system-wide default configuration.
@@ -371,6 +372,13 @@ advice.*::
372 editor input from the user.
373 --
374
375 +extensions.worktreeConfig::
376 + If set, by default "git config" reads from both "config" and
377 + "config.worktree" file from GIT_DIR in that order. In
378 + multiple working directory mode, "config" file is shared while
379 + "config.worktree" is per-working directory (i.e., it's in
380 + GIT_COMMON_DIR/worktrees/<id>/config.worktree)
381 +
382 core.fileMode::
383 Tells Git if the executable bit of files in the working tree
384 is to be honored.
Documentation/git-config.txt
+19 -7
@@ -45,13 +45,15 @@ unset an existing `--type` specifier with `--no-type`.
45
46 When reading, the values are read from the system, global and
47 repository local configuration files by default, and options
48 -`--system`, `--global`, `--local` and `--file <filename>` can be
49 -used to tell the command to read from only that location (see <<FILES>>).
48 +`--system`, `--global`, `--local`, `--worktree` and
49 +`--file <filename>` can be used to tell the command to read from only
50 +that location (see <<FILES>>).
51
52 When writing, the new value is written to the repository local
53 configuration file by default, and options `--system`, `--global`,
53 -`--file <filename>` can be used to tell the command to write to
54 -that location (you can say `--local` but that is the default).
54 +`--worktree`, `--file <filename>` can be used to tell the command to
55 +write to that location (you can say `--local` but that is the
56 +default).
57
58 This command will fail with non-zero status upon error. Some exit
59 codes are:
@@ -131,6 +133,11 @@ from all available files.
133 +
134 See also <<FILES>>.
135
136 +--worktree::
137 + Similar to `--local` except that `.git/config.worktree` is
138 + read from or written to if `extensions.worktreeConfig` is
139 + present. If not it's the same as `--local`.
140 +
141 -f config-file::
142 --file config-file::
143 Use the given config file instead of the one specified by GIT_CONFIG.
@@ -281,6 +288,10 @@ $XDG_CONFIG_HOME/git/config::
288 $GIT_DIR/config::
289 Repository specific configuration file.
290
291 +$GIT_DIR/config.worktree::
292 + This is optional and is only searched when
293 + `extensions.worktreeConfig` is present in $GIT_DIR/config.
294 +
295 If no further options are given, all reading options will read all of these
296 files that are available. If the global or the system-wide configuration
297 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`
310 and `--unset`. *'git config' will only ever change one file at a time*.
311
312 You can override these rules either by command-line options or by environment
302 -variables. The `--global` and the `--system` options will limit the file used
303 -to the global or system-wide file respectively. The `GIT_CONFIG` environment
304 -variable has a similar effect, but you can specify any filename you want.
313 +variables. The `--global`, `--system` and `--worktree` options will limit
314 +the file used to the global, system-wide or per-worktree file respectively.
315 +The `GIT_CONFIG` environment variable has a similar effect, but you
316 +can specify any filename you want.
317
318
319 ENVIRONMENT
Documentation/git-worktree.txt
+33
@@ -204,6 +204,36 @@ working trees, it can be used to identify worktrees. For example if
204 you only have two working trees, at "/abc/def/ghi" and "/abc/def/ggg",
205 then "ghi" or "def/ghi" is enough to point to the former working tree.
206
207 +CONFIGURATION FILE
208 +------------------
209 +By default, the repository "config" file is shared across all working
210 +trees. If the config variables `core.bare` or `core.worktree` are
211 +already present in the config file, they will be applied to the main
212 +working trees only.
213 +
214 +In order to have configuration specific to working trees, you can turn
215 +on "worktreeConfig" extension, e.g.:
216 +
217 +------------
218 +$ git config extensions.worktreeConfig true
219 +------------
220 +
221 +In this mode, specific configuration stays in the path pointed by `git
222 +rev-parse --git-path config.worktree`. You can add or update
223 +configuration in this file with `git config --worktree`. Older Git
224 +versions will refuse to access repositories with this extension.
225 +
226 +Note that in this file, the exception for `core.bare` and `core.worktree`
227 +is gone. If you have them in $GIT_DIR/config before, you must move
228 +them to the `config.worktree` of the main working tree. You may also
229 +take this opportunity to review and move other configuration that you
230 +do not want to share to all working trees:
231 +
232 + - `core.worktree` and `core.bare` should never be shared
233 +
234 + - `core.sparseCheckout` is recommended per working tree, unless you
235 + are sure you always use sparse checkout for all working trees.
236 +
237 DETAILS
238 -------
239 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
283 `test-next` entry from being pruned. See
284 linkgit:gitrepository-layout[5] for details.
285
286 +When extensions.worktreeConfig is enabled, the config file
287 +`.git/worktrees/<id>/config.worktree` is read after `.git/config` is.
288 +
289 LIST OUTPUT FORMAT
290 ------------------
291 The worktree list command has two output formats. The default format shows the
Documentation/gitrepository-layout.txt
+8
@@ -143,6 +143,11 @@ config::
143 if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
144 used instead.
145
146 +config.worktree::
147 + Working directory specific configuration file for the main
148 + working directory in multiple working directory setup (see
149 + linkgit:git-worktree[1]).
150 +
151 branches::
152 A slightly deprecated way to store shorthands to be used
153 to specify a URL to 'git fetch', 'git pull' and 'git push'.
@@ -275,6 +280,9 @@ worktrees/<id>/locked::
280 or manually by `git worktree prune`. The file may contain a string
281 explaining why the repository is locked.
282
283 +worktrees/<id>/config.worktree::
284 + Working directory specific configuration file.
285 +
286 SEE ALSO
287 --------
288 linkgit:git-init[1],
builtin/config.c
+18 -1
@@ -5,6 +5,7 @@
5 #include "parse-options.h"
6 #include "urlmatch.h"
7 #include "quote.h"
8 +#include "worktree.h"
9
10 static const char *const builtin_config_usage[] = {
11 N_("git config [<options>]"),
@@ -24,6 +25,7 @@ static char key_delim = ' ';
25 static char term = '\n';
26
27 static int use_global_config, use_system_config, use_local_config;
28 +static int use_worktree_config;
29 static struct git_config_source given_config_source;
30 static int actions, type;
31 static char *default_value;
@@ -123,6 +125,7 @@ static struct option builtin_config_options[] = {
125 OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
126 OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
127 OPT_BOOL(0, "local", &use_local_config, N_("use repository config file")),
128 + OPT_BOOL(0, "worktree", &use_worktree_config, N_("use per-worktree config file")),
129 OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
130 OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
131 OPT_GROUP(N_("Action")),
@@ -602,6 +605,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
605 PARSE_OPT_STOP_AT_NON_OPTION);
606
607 if (use_global_config + use_system_config + use_local_config +
608 + use_worktree_config +
609 !!given_config_source.file + !!given_config_source.blob > 1) {
610 error(_("only one config file at a time"));
611 usage_builtin_config();
@@ -645,7 +649,20 @@ int cmd_config(int argc, const char **argv, const char *prefix)
649 given_config_source.file = git_etc_gitconfig();
650 else if (use_local_config)
651 given_config_source.file = git_pathdup("config");
648 - else if (given_config_source.file) {
652 + else if (use_worktree_config) {
653 + struct worktree **worktrees = get_worktrees(0);
654 + if (repository_format_worktree_config)
655 + given_config_source.file = git_pathdup("config.worktree");
656 + else if (worktrees[0] && worktrees[1])
657 + die(_("--worktree cannot be used with multiple "
658 + "working trees unless the config\n"
659 + "extension worktreeConfig is enabled. "
660 + "Please read \"CONFIGURATION FILE\"\n"
661 + "section in \"git help worktree\" for details"));
662 + else
663 + given_config_source.file = git_pathdup("config");
664 + free_worktrees(worktrees);
665 + } else if (given_config_source.file) {
666 if (!is_absolute_path(given_config_source.file) && prefix)
667 given_config_source.file =
668 prefix_filename(prefix, given_config_source.file);
cache.h
+2
@@ -960,11 +960,13 @@ extern int grafts_replace_parents;
960 extern int repository_format_precious_objects;
961 extern char *repository_format_partial_clone;
962 extern const char *core_partial_clone_filter_default;
963 +extern int repository_format_worktree_config;
964
965 struct repository_format {
966 int version;
967 int precious_objects;
968 char *partial_clone; /* value of extensions.partialclone */
969 + int worktree_config;
970 int is_bare;
971 int hash_algo;
972 char *work_tree;
config.c
+11
@@ -1695,6 +1695,17 @@ static int do_git_config_sequence(const struct config_options *opts,
1695 if (repo_config && !access_or_die(repo_config, R_OK, 0))
1696 ret += git_config_from_file(fn, repo_config, data);
1697
1698 + /*
1699 + * Note: this should have a new scope, CONFIG_SCOPE_WORKTREE.
1700 + * But let's not complicate things before it's actually needed.
1701 + */
1702 + if (repository_format_worktree_config) {
1703 + char *path = git_pathdup("config.worktree");
1704 + if (!access_or_die(path, R_OK, 0))
1705 + ret += git_config_from_file(fn, path, data);
1706 + free(path);
1707 + }
1708 +
1709 current_parsing_scope = CONFIG_SCOPE_CMDLINE;
1710 if (git_config_from_parameters(fn, data) < 0)
1711 die(_("unable to parse command-line config"));
environment.c
+1
@@ -33,6 +33,7 @@ int ref_paranoia = -1;
33 int repository_format_precious_objects;
34 char *repository_format_partial_clone;
35 const char *core_partial_clone_filter_default;
36 +int repository_format_worktree_config;
37 const char *git_commit_encoding;
38 const char *git_log_output_encoding;
39 const char *apply_default_whitespace;
setup.c
+32 -8
@@ -402,6 +402,20 @@ void setup_work_tree(void)
402 initialized = 1;
403 }
404
405 +static int read_worktree_config(const char *var, const char *value, void *vdata)
406 +{
407 + struct repository_format *data = vdata;
408 +
409 + if (strcmp(var, "core.bare") == 0) {
410 + data->is_bare = git_config_bool(var, value);
411 + } else if (strcmp(var, "core.worktree") == 0) {
412 + if (!value)
413 + return config_error_nonbool(var);
414 + data->work_tree = xstrdup(value);
415 + }
416 + return 0;
417 +}
418 +
419 static int check_repo_format(const char *var, const char *value, void *vdata)
420 {
421 struct repository_format *data = vdata;
@@ -423,16 +437,13 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
437 if (!value)
438 return config_error_nonbool(var);
439 data->partial_clone = xstrdup(value);
426 - } else
440 + } else if (!strcmp(ext, "worktreeconfig"))
441 + data->worktree_config = git_config_bool(var, value);
442 + else
443 string_list_append(&data->unknown_extensions, ext);
428 - } else if (strcmp(var, "core.bare") == 0) {
429 - data->is_bare = git_config_bool(var, value);
430 - } else if (strcmp(var, "core.worktree") == 0) {
431 - if (!value)
432 - return config_error_nonbool(var);
433 - data->work_tree = xstrdup(value);
444 }
435 - return 0;
445 +
446 + return read_worktree_config(var, value, vdata);
447 }
448
449 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_
477
478 repository_format_precious_objects = candidate->precious_objects;
479 repository_format_partial_clone = candidate->partial_clone;
480 + repository_format_worktree_config = candidate->worktree_config;
481 string_list_clear(&candidate->unknown_extensions, 0);
482 +
483 + if (repository_format_worktree_config) {
484 + /*
485 + * pick up core.bare and core.worktree from per-worktree
486 + * config if present
487 + */
488 + strbuf_addf(&sb, "%s/config.worktree", gitdir);
489 + git_config_from_file(read_worktree_config, sb.buf, candidate);
490 + strbuf_release(&sb);
491 + has_common = 0;
492 + }
493 +
494 if (!has_common) {
495 if (candidate->is_bare != -1) {
496 is_bare_repository_cfg = candidate->is_bare;
t/t2029-worktree-config.sh new
+79
@@ -0,0 +1,79 @@
1 +#!/bin/sh
2 +
3 +test_description="config file in multi worktree"
4 +
5 +. ./test-lib.sh
6 +
7 +test_expect_success 'setup' '
8 + test_commit start
9 +'
10 +
11 +test_expect_success 'config --worktree in single worktree' '
12 + git config --worktree foo.bar true &&
13 + test_cmp_config true foo.bar
14 +'
15 +
16 +test_expect_success 'add worktrees' '
17 + git worktree add wt1 &&
18 + git worktree add wt2
19 +'
20 +
21 +test_expect_success 'config --worktree without extension' '
22 + test_must_fail git config --worktree foo.bar false
23 +'
24 +
25 +test_expect_success 'enable worktreeConfig extension' '
26 + git config extensions.worktreeConfig true &&
27 + test_cmp_config true extensions.worktreeConfig
28 +'
29 +
30 +test_expect_success 'config is shared as before' '
31 + git config this.is shared &&
32 + test_cmp_config shared this.is &&
33 + test_cmp_config -C wt1 shared this.is &&
34 + test_cmp_config -C wt2 shared this.is
35 +'
36 +
37 +test_expect_success 'config is shared (set from another worktree)' '
38 + git -C wt1 config that.is also-shared &&
39 + test_cmp_config also-shared that.is &&
40 + test_cmp_config -C wt1 also-shared that.is &&
41 + test_cmp_config -C wt2 also-shared that.is
42 +'
43 +
44 +test_expect_success 'config private to main worktree' '
45 + git config --worktree this.is for-main &&
46 + test_cmp_config for-main this.is &&
47 + test_cmp_config -C wt1 shared this.is &&
48 + test_cmp_config -C wt2 shared this.is
49 +'
50 +
51 +test_expect_success 'config private to linked worktree' '
52 + git -C wt1 config --worktree this.is for-wt1 &&
53 + test_cmp_config for-main this.is &&
54 + test_cmp_config -C wt1 for-wt1 this.is &&
55 + test_cmp_config -C wt2 shared this.is
56 +'
57 +
58 +test_expect_success 'core.bare no longer for main only' '
59 + test_config core.bare true &&
60 + test "$(git rev-parse --is-bare-repository)" = true &&
61 + test "$(git -C wt1 rev-parse --is-bare-repository)" = true &&
62 + test "$(git -C wt2 rev-parse --is-bare-repository)" = true
63 +'
64 +
65 +test_expect_success 'per-worktree core.bare is picked up' '
66 + git -C wt1 config --worktree core.bare true &&
67 + test "$(git rev-parse --is-bare-repository)" = false &&
68 + test "$(git -C wt1 rev-parse --is-bare-repository)" = true &&
69 + test "$(git -C wt2 rev-parse --is-bare-repository)" = false
70 +'
71 +
72 +test_expect_success 'config.worktree no longer read without extension' '
73 + git config --unset extensions.worktreeConfig &&
74 + test_cmp_config shared this.is &&
75 + test_cmp_config -C wt1 shared this.is &&
76 + test_cmp_config -C wt2 shared this.is
77 +'
78 +
79 +test_done