Skip to content

Commit 16dc37c

Browse files
committed
Merge branch 'cl/conditional-config-on-worktree-path' into jch
The [includeIf "condition"] conditional inclusion facility for configuration files has learned to use the location of worktree in its condition. Comments? * cl/conditional-config-on-worktree-path: config: add "worktree" and "worktree/i" includeIf conditions config: refactor include_by_gitdir() into include_by_path()
2 parents 61ea95c + da1f520 commit 16dc37c

File tree

3 files changed

+130
-11
lines changed

3 files changed

+130
-11
lines changed

Documentation/config.adoc

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,48 @@ refer to linkgit:gitignore[5] for details. For convenience:
146146
This is the same as `gitdir` except that matching is done
147147
case-insensitively (e.g. on case-insensitive file systems)
148148

149+
`worktree`::
150+
The data that follows the keyword `worktree` and a colon is used as a
151+
glob pattern. If the working directory of the current worktree matches
152+
the pattern, the include condition is met.
153+
+
154+
The worktree location is the path where files are checked out (as returned
155+
by `git rev-parse --show-toplevel`). This is different from `gitdir`, which
156+
matches the `.git` directory path. In a linked worktree, the worktree path
157+
is the directory where that worktree's files are located, not the main
158+
repository's `.git` directory.
159+
+
160+
The pattern can contain standard globbing wildcards and two additional
161+
ones, `**/` and `/**`, that can match multiple path components. Please
162+
refer to linkgit:gitignore[5] for details. For convenience:
163+
164+
* If the pattern starts with `~/`, `~` will be substituted with the
165+
content of the environment variable `HOME`.
166+
167+
* If the pattern starts with `./`, it is replaced with the directory
168+
containing the current config file.
169+
170+
* If the pattern does not start with either `~/`, `./` or `/`, `**/`
171+
will be automatically prepended. For example, the pattern `foo/bar`
172+
becomes `**/foo/bar` and would match `/any/path/to/foo/bar`.
173+
174+
* If the pattern ends with `/`, `**` will be automatically added. For
175+
example, the pattern `foo/` becomes `foo/**`. In other words, it
176+
matches "foo" and everything inside, recursively.
177+
+
178+
This condition will never match in a bare repository (which has no worktree).
179+
+
180+
This is useful when you need to use different `user.name`, `user.email`, or
181+
GPG keys in different worktrees of the same repository. While
182+
`extensions.worktreeConfig` also allows per-worktree configuration, it
183+
requires changes inside each repository. This condition can be set in the
184+
user's global configuration file (e.g. `~/.config/git/config`) and applies
185+
to multiple repositories at once.
186+
187+
`worktree/i`::
188+
This is the same as `worktree` except that matching is done
189+
case-insensitively (e.g. on case-insensitive file systems)
190+
149191
`onbranch`::
150192
The data that follows the keyword `onbranch` and a colon is taken to be a
151193
pattern with standard globbing wildcards and two additional
@@ -244,6 +286,14 @@ Example
244286
[includeIf "gitdir:~/to/group/"]
245287
path = /path/to/foo.inc
246288
289+
; include if the worktree is at /path/to/project-build
290+
[includeIf "worktree:/path/to/project-build"]
291+
path = build-config.inc
292+
293+
; include for all worktrees inside /path/to/group
294+
[includeIf "worktree:/path/to/group/"]
295+
path = group-config.inc
296+
247297
; relative paths are always relative to the including
248298
; file (if the condition is true); their location is not
249299
; affected by the condition

config.c

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi,
235235
return 0;
236236
}
237237

238-
static int include_by_gitdir(const struct key_value_info *kvi,
239-
const struct config_options *opts,
240-
const char *cond, size_t cond_len, int icase)
238+
static int include_by_path(const struct key_value_info *kvi,
239+
const char *path,
240+
const char *cond, size_t cond_len, int icase)
241241
{
242242
struct strbuf text = STRBUF_INIT;
243243
struct strbuf pattern = STRBUF_INIT;
244244
size_t prefix;
245245
int ret = 0;
246-
const char *git_dir;
247246
int already_tried_absolute = 0;
248247

249-
if (opts->git_dir)
250-
git_dir = opts->git_dir;
251-
else
248+
if (!path)
252249
goto done;
253250

254-
strbuf_realpath(&text, git_dir, 1);
251+
strbuf_realpath(&text, path, 1);
255252
strbuf_add(&pattern, cond, cond_len);
256253
ret = prepare_include_condition_pattern(kvi, &pattern, &prefix);
257254
if (ret < 0)
@@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi,
284281
* which'll do the right thing
285282
*/
286283
strbuf_reset(&text);
287-
strbuf_add_absolute_path(&text, git_dir);
284+
strbuf_add_absolute_path(&text, path);
288285
already_tried_absolute = 1;
289286
goto again;
290287
}
@@ -400,9 +397,15 @@ static int include_condition_is_true(const struct key_value_info *kvi,
400397
const struct config_options *opts = inc->opts;
401398

402399
if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len))
403-
return include_by_gitdir(kvi, opts, cond, cond_len, 0);
400+
return include_by_path(kvi, opts->git_dir, cond, cond_len, 0);
404401
else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len))
405-
return include_by_gitdir(kvi, opts, cond, cond_len, 1);
402+
return include_by_path(kvi, opts->git_dir, cond, cond_len, 1);
403+
else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len))
404+
return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
405+
cond, cond_len, 0);
406+
else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len))
407+
return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,
408+
cond, cond_len, 1);
406409
else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len))
407410
return include_by_branch(inc, cond, cond_len);
408411
else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond,

t/t1305-config-include.sh

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,4 +396,70 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di
396396
test_must_fail nongit git --git-dir=nonexistent config get foo.bar
397397
'
398398

399+
# worktree: conditional include tests
400+
401+
test_expect_success 'conditional include, worktree bare repo' '
402+
git init --bare wt-bare &&
403+
(
404+
cd wt-bare &&
405+
echo "[includeIf \"worktree:/\"]path=bar-bare" >>config &&
406+
echo "[test]wtbare=1" >bar-bare &&
407+
test_must_fail git config test.wtbare
408+
)
409+
'
410+
411+
test_expect_success 'conditional include, worktree multiple worktrees' '
412+
git init wt-multi &&
413+
(
414+
cd wt-multi &&
415+
test_commit initial &&
416+
git worktree add -b linked-branch ../wt-linked HEAD &&
417+
git worktree add -b prefix-branch ../wt-prefix/linked HEAD
418+
) &&
419+
wt_main="$(cd wt-multi && pwd)" &&
420+
wt_linked="$(cd wt-linked && pwd)" &&
421+
wt_prefix_parent="$(cd wt-prefix && pwd)" &&
422+
cat >>wt-multi/.git/config <<-EOF &&
423+
[includeIf "worktree:$wt_main"]
424+
path = main-config
425+
[includeIf "worktree:$wt_linked"]
426+
path = linked-config
427+
[includeIf "worktree:$wt_prefix_parent/"]
428+
path = prefix-config
429+
EOF
430+
echo "[test]mainvar=main" >wt-multi/.git/main-config &&
431+
echo "[test]linkedvar=linked" >wt-multi/.git/linked-config &&
432+
echo "[test]prefixvar=prefix" >wt-multi/.git/prefix-config &&
433+
echo main >expect &&
434+
git -C wt-multi config test.mainvar >actual &&
435+
test_cmp expect actual &&
436+
test_must_fail git -C wt-multi config test.linkedvar &&
437+
test_must_fail git -C wt-multi config test.prefixvar &&
438+
echo linked >expect &&
439+
git -C wt-linked config test.linkedvar >actual &&
440+
test_cmp expect actual &&
441+
test_must_fail git -C wt-linked config test.mainvar &&
442+
test_must_fail git -C wt-linked config test.prefixvar &&
443+
echo prefix >expect &&
444+
git -C wt-prefix/linked config test.prefixvar >actual &&
445+
test_cmp expect actual &&
446+
test_must_fail git -C wt-prefix/linked config test.mainvar &&
447+
test_must_fail git -C wt-prefix/linked config test.linkedvar
448+
'
449+
450+
test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '
451+
mkdir real-wt &&
452+
ln -s real-wt link-wt &&
453+
git init link-wt/repo &&
454+
(
455+
cd link-wt/repo &&
456+
# repo->worktree resolves symlinks, so use real path in pattern
457+
echo "[includeIf \"worktree:**/real-wt/repo\"]path=bar-link" >>.git/config &&
458+
echo "[test]wtlink=2" >.git/bar-link &&
459+
echo 2 >expect &&
460+
git config test.wtlink >actual &&
461+
test_cmp expect actual
462+
)
463+
'
464+
399465
test_done

0 commit comments

Comments
 (0)