Skip to content

Commit 58b284a

Browse files
pcloudsgitster
authored andcommitted
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 <[email protected]> Signed-off-by: Junio C Hamano <[email protected]>
1 parent a5db0b7 commit 58b284a

File tree

10 files changed

+213
-18
lines changed

10 files changed

+213
-18
lines changed

Documentation/config.txt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,9 @@ CONFIGURATION FILE
22
------------------
33

44
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
78
`$HOME/.gitconfig` is used to store a per-user configuration as
89
fallback values for the `.git/config` file. The file `/etc/gitconfig`
910
can be used to store a system-wide default configuration.
@@ -371,6 +372,13 @@ advice.*::
371372
editor input from the user.
372373
--
373374

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+
374382
core.fileMode::
375383
Tells Git if the executable bit of files in the working tree
376384
is to be honored.

Documentation/git-config.txt

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,13 +45,15 @@ unset an existing `--type` specifier with `--no-type`.
4545

4646
When reading, the values are read from the system, global and
4747
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>>).
5051

5152
When writing, the new value is written to the repository local
5253
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).
5557

5658
This command will fail with non-zero status upon error. Some exit
5759
codes are:
@@ -131,6 +133,11 @@ from all available files.
131133
+
132134
See also <<FILES>>.
133135

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+
134141
-f config-file::
135142
--file config-file::
136143
Use the given config file instead of the one specified by GIT_CONFIG.
@@ -281,6 +288,10 @@ $XDG_CONFIG_HOME/git/config::
281288
$GIT_DIR/config::
282289
Repository specific configuration file.
283290

291+
$GIT_DIR/config.worktree::
292+
This is optional and is only searched when
293+
`extensions.worktreeConfig` is present in $GIT_DIR/config.
294+
284295
If no further options are given, all reading options will read all of these
285296
files that are available. If the global or the system-wide configuration
286297
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`
299310
and `--unset`. *'git config' will only ever change one file at a time*.
300311

301312
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.
305317

306318

307319
ENVIRONMENT

Documentation/git-worktree.txt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,36 @@ working trees, it can be used to identify worktrees. For example if
204204
you only have two working trees, at "/abc/def/ghi" and "/abc/def/ggg",
205205
then "ghi" or "def/ghi" is enough to point to the former working tree.
206206

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+
207237
DETAILS
208238
-------
209239
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
253283
`test-next` entry from being pruned. See
254284
linkgit:gitrepository-layout[5] for details.
255285

286+
When extensions.worktreeConfig is enabled, the config file
287+
`.git/worktrees/<id>/config.worktree` is read after `.git/config` is.
288+
256289
LIST OUTPUT FORMAT
257290
------------------
258291
The worktree list command has two output formats. The default format shows the

Documentation/gitrepository-layout.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ config::
143143
if $GIT_COMMON_DIR is set and "$GIT_COMMON_DIR/config" will be
144144
used instead.
145145

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+
146151
branches::
147152
A slightly deprecated way to store shorthands to be used
148153
to specify a URL to 'git fetch', 'git pull' and 'git push'.
@@ -275,6 +280,9 @@ worktrees/<id>/locked::
275280
or manually by `git worktree prune`. The file may contain a string
276281
explaining why the repository is locked.
277282

283+
worktrees/<id>/config.worktree::
284+
Working directory specific configuration file.
285+
278286
SEE ALSO
279287
--------
280288
linkgit:git-init[1],

builtin/config.c

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include "parse-options.h"
66
#include "urlmatch.h"
77
#include "quote.h"
8+
#include "worktree.h"
89

910
static const char *const builtin_config_usage[] = {
1011
N_("git config [<options>]"),
@@ -24,6 +25,7 @@ static char key_delim = ' ';
2425
static char term = '\n';
2526

2627
static int use_global_config, use_system_config, use_local_config;
28+
static int use_worktree_config;
2729
static struct git_config_source given_config_source;
2830
static int actions, type;
2931
static char *default_value;
@@ -123,6 +125,7 @@ static struct option builtin_config_options[] = {
123125
OPT_BOOL(0, "global", &use_global_config, N_("use global config file")),
124126
OPT_BOOL(0, "system", &use_system_config, N_("use system config file")),
125127
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")),
126129
OPT_STRING('f', "file", &given_config_source.file, N_("file"), N_("use given config file")),
127130
OPT_STRING(0, "blob", &given_config_source.blob, N_("blob-id"), N_("read config from given blob object")),
128131
OPT_GROUP(N_("Action")),
@@ -602,6 +605,7 @@ int cmd_config(int argc, const char **argv, const char *prefix)
602605
PARSE_OPT_STOP_AT_NON_OPTION);
603606

604607
if (use_global_config + use_system_config + use_local_config +
608+
use_worktree_config +
605609
!!given_config_source.file + !!given_config_source.blob > 1) {
606610
error(_("only one config file at a time"));
607611
usage_builtin_config();
@@ -645,7 +649,20 @@ int cmd_config(int argc, const char **argv, const char *prefix)
645649
given_config_source.file = git_etc_gitconfig();
646650
else if (use_local_config)
647651
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) {
649666
if (!is_absolute_path(given_config_source.file) && prefix)
650667
given_config_source.file =
651668
prefix_filename(prefix, given_config_source.file);

cache.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -960,11 +960,13 @@ extern int grafts_replace_parents;
960960
extern int repository_format_precious_objects;
961961
extern char *repository_format_partial_clone;
962962
extern const char *core_partial_clone_filter_default;
963+
extern int repository_format_worktree_config;
963964

964965
struct repository_format {
965966
int version;
966967
int precious_objects;
967968
char *partial_clone; /* value of extensions.partialclone */
969+
int worktree_config;
968970
int is_bare;
969971
int hash_algo;
970972
char *work_tree;

config.c

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1695,6 +1695,17 @@ static int do_git_config_sequence(const struct config_options *opts,
16951695
if (repo_config && !access_or_die(repo_config, R_OK, 0))
16961696
ret += git_config_from_file(fn, repo_config, data);
16971697

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+
16981709
current_parsing_scope = CONFIG_SCOPE_CMDLINE;
16991710
if (git_config_from_parameters(fn, data) < 0)
17001711
die(_("unable to parse command-line config"));

environment.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ int ref_paranoia = -1;
3333
int repository_format_precious_objects;
3434
char *repository_format_partial_clone;
3535
const char *core_partial_clone_filter_default;
36+
int repository_format_worktree_config;
3637
const char *git_commit_encoding;
3738
const char *git_log_output_encoding;
3839
const char *apply_default_whitespace;

setup.c

Lines changed: 32 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -402,6 +402,20 @@ void setup_work_tree(void)
402402
initialized = 1;
403403
}
404404

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+
405419
static int check_repo_format(const char *var, const char *value, void *vdata)
406420
{
407421
struct repository_format *data = vdata;
@@ -423,16 +437,13 @@ static int check_repo_format(const char *var, const char *value, void *vdata)
423437
if (!value)
424438
return config_error_nonbool(var);
425439
data->partial_clone = xstrdup(value);
426-
} else
440+
} else if (!strcmp(ext, "worktreeconfig"))
441+
data->worktree_config = git_config_bool(var, value);
442+
else
427443
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);
434444
}
435-
return 0;
445+
446+
return read_worktree_config(var, value, vdata);
436447
}
437448

438449
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_
466477

467478
repository_format_precious_objects = candidate->precious_objects;
468479
repository_format_partial_clone = candidate->partial_clone;
480+
repository_format_worktree_config = candidate->worktree_config;
469481
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+
470494
if (!has_common) {
471495
if (candidate->is_bare != -1) {
472496
is_bare_repository_cfg = candidate->is_bare;

t/t2029-worktree-config.sh

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

0 commit comments

Comments
 (0)