* [PATCH v2 0/3] includeIf: add "worktree" condition for matching working tree path
@ 2026-04-02 2:58 Chen Linxuan via B4 Relay
2026-04-02 2:58 ` [PATCH v2 1/3] config: add "worktree" and "worktree/i" includeIf conditions Chen Linxuan via B4 Relay
` (2 more replies)
0 siblings, 3 replies; 5+ messages in thread
From: Chen Linxuan via B4 Relay @ 2026-04-02 2:58 UTC (permalink / raw)
To: git; +Cc: Kristoffer Haugsbakk, Chen Linxuan
The `includeIf` mechanism already supports matching on the `.git`
directory path (`gitdir`) and the currently checked out branch
(`onbranch`). But in multi-worktree setups the `.git` directory of a
linked worktree points into the main repository's `.git/worktrees/`
area, which makes `gitdir` patterns cumbersome when one wants to
include config based on the working tree's checkout path instead.
Introduce two new condition keywords:
- `worktree:<pattern>` matches the realpath of the current worktree's
working directory against a glob pattern.
- `worktree/i:<pattern>` is the case-insensitive variant.
Supported pattern features: glob wildcards, `**/` and `/**`, `~`
expansion, `./` relative paths, and trailing-`/` prefix matching.
The condition never matches in a bare repository.
Signed-off-by: Chen Linxuan <me@black-desk.cn>
---
Changes in v2:
- Add missing signed-off-by lines.
- Link to v1: https://lore.kernel.org/r/20260401-includeif-worktree-v1-0-906db69f2c79@black-desk.cn
---
Chen Linxuan (3):
config: add "worktree" and "worktree/i" includeIf conditions
Documentation/config: add includeIf "worktree"
t1305: add tests for includeIf "worktree"
Documentation/config.adoc | 50 +++++++++++++++++++++++++++++++++++
config.c | 25 ++++++++++--------
t/t1305-config-include.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++++
3 files changed, 130 insertions(+), 11 deletions(-)
---
base-commit: 270e10ad6dda3379ea0da7efd11e4fbf2cd7a325
change-id: 20260401-includeif-worktree-fcb64950dfba
Best regards,
--
Chen Linxuan <me@black-desk.cn>
^ permalink raw reply [flat|nested] 5+ messages in thread* [PATCH v2 1/3] config: add "worktree" and "worktree/i" includeIf conditions 2026-04-02 2:58 [PATCH v2 0/3] includeIf: add "worktree" condition for matching working tree path Chen Linxuan via B4 Relay @ 2026-04-02 2:58 ` Chen Linxuan via B4 Relay 2026-04-02 21:22 ` Junio C Hamano 2026-04-02 2:58 ` [PATCH v2 2/3] Documentation/config: add includeIf "worktree" Chen Linxuan via B4 Relay 2026-04-02 2:58 ` [PATCH v2 3/3] t1305: add tests for " Chen Linxuan via B4 Relay 2 siblings, 1 reply; 5+ messages in thread From: Chen Linxuan via B4 Relay @ 2026-04-02 2:58 UTC (permalink / raw) To: git; +Cc: Kristoffer Haugsbakk, Chen Linxuan From: Chen Linxuan <me@black-desk.cn> The `includeIf` mechanism already supports matching on the `.git` directory path (`gitdir`) and the currently checked out branch (`onbranch`). But in multi-worktree setups the `.git` directory of a linked worktree points into the main repository's `.git/worktrees/` area, which makes `gitdir` patterns cumbersome when one wants to include config based on the working tree's checkout path instead. Introduce two new condition keywords: - `worktree:<pattern>` matches the realpath of the current worktree's working directory (i.e. `repo_get_work_tree()`) against a glob pattern. This is the path returned by `git rev-parse --show-toplevel`. - `worktree/i:<pattern>` is the case-insensitive variant. The implementation follows the same structure as `include_by_gitdir()`: the worktree path is resolved via `strbuf_realpath()`, the condition pattern is prepared with `prepare_include_condition_pattern()` (which handles `~` expansion, `./` relative paths, `**/` prefix insertion and trailing-`/` expansion), and matching is done with `wildmatch()`. A second attempt using `strbuf_add_absolute_path()` is performed to handle symlinked paths. The condition never matches in bare repositories (where there is no worktree) or during early config reading (where no repository is available). Signed-off-by: Chen Linxuan <me@black-desk.cn> --- config.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/config.c b/config.c index 156f2a24fa00..6d0c2d0725e4 100644 --- a/config.c +++ b/config.c @@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi, return 0; } -static int include_by_gitdir(const struct key_value_info *kvi, - const struct config_options *opts, - const char *cond, size_t cond_len, int icase) +static int include_by_path(const struct key_value_info *kvi, + const char *path, + const char *cond, size_t cond_len, int icase) { struct strbuf text = STRBUF_INIT; struct strbuf pattern = STRBUF_INIT; size_t prefix; int ret = 0; - const char *git_dir; int already_tried_absolute = 0; - if (opts->git_dir) - git_dir = opts->git_dir; - else + if (!path) goto done; - strbuf_realpath(&text, git_dir, 1); + strbuf_realpath(&text, path, 1); strbuf_add(&pattern, cond, cond_len); ret = prepare_include_condition_pattern(kvi, &pattern, &prefix); if (ret < 0) @@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi, * which'll do the right thing */ strbuf_reset(&text); - strbuf_add_absolute_path(&text, git_dir); + strbuf_add_absolute_path(&text, path); already_tried_absolute = 1; goto again; } @@ -400,9 +397,15 @@ static int include_condition_is_true(const struct key_value_info *kvi, const struct config_options *opts = inc->opts; if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) - return include_by_gitdir(kvi, opts, cond, cond_len, 0); + return include_by_path(kvi, opts->git_dir, cond, cond_len, 0); else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len)) - return include_by_gitdir(kvi, opts, cond, cond_len, 1); + return include_by_path(kvi, opts->git_dir, cond, cond_len, 1); + else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len)) + return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL, + cond, cond_len, 0); + else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len)) + return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL, + cond, cond_len, 1); else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len)) return include_by_branch(inc, cond, cond_len); else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond, -- 2.53.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/3] config: add "worktree" and "worktree/i" includeIf conditions 2026-04-02 2:58 ` [PATCH v2 1/3] config: add "worktree" and "worktree/i" includeIf conditions Chen Linxuan via B4 Relay @ 2026-04-02 21:22 ` Junio C Hamano 0 siblings, 0 replies; 5+ messages in thread From: Junio C Hamano @ 2026-04-02 21:22 UTC (permalink / raw) To: Chen Linxuan via B4 Relay; +Cc: git, Kristoffer Haugsbakk, Chen Linxuan Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org> writes: > From: Chen Linxuan <me@black-desk.cn> > > The `includeIf` mechanism already supports matching on the `.git` > directory path (`gitdir`) and the currently checked out branch > (`onbranch`). But in multi-worktree setups the `.git` directory of a > linked worktree points into the main repository's `.git/worktrees/` > area, which makes `gitdir` patterns cumbersome when one wants to > include config based on the working tree's checkout path instead. > > Introduce two new condition keywords: > > - `worktree:<pattern>` matches the realpath of the current worktree's > working directory (i.e. `repo_get_work_tree()`) against a glob > pattern. This is the path returned by `git rev-parse > --show-toplevel`. > > - `worktree/i:<pattern>` is the case-insensitive variant. > > The implementation follows the same structure as `include_by_gitdir()`: > the worktree path is resolved via `strbuf_realpath()`, the condition > pattern is prepared with `prepare_include_condition_pattern()` (which > handles `~` expansion, `./` relative paths, `**/` prefix insertion and > trailing-`/` expansion), and matching is done with `wildmatch()`. A > second attempt using `strbuf_add_absolute_path()` is performed to > handle symlinked paths. > > The condition never matches in bare repositories (where there is no > worktree) or during early config reading (where no repository is > available). > > Signed-off-by: Chen Linxuan <me@black-desk.cn> > --- > config.c | 25 ++++++++++++++----------- > 1 file changed, 14 insertions(+), 11 deletions(-) This probably should be further split into two commits, one that introduces the "by-path" helper function and reimplements the "gitdir" and "gitdir/i" support with it (i.e., essentially it amounts to dropping of "struct config_options *" and using "const char *path" in place of it, I presume), and another patch that adds worktree support in terms of the "by-path" helper. The documentation updates and additional test should probably be done as part of the second half of this patch, unless the second half of this patch to add "worktree" and "worktree/i" is too big to be reviewed standalone (which I do not think would be the case). > diff --git a/config.c b/config.c > index 156f2a24fa00..6d0c2d0725e4 100644 > --- a/config.c > +++ b/config.c > @@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi, > return 0; > } > > -static int include_by_gitdir(const struct key_value_info *kvi, > - const struct config_options *opts, > - const char *cond, size_t cond_len, int icase) > +static int include_by_path(const struct key_value_info *kvi, > + const char *path, > + const char *cond, size_t cond_len, int icase) > { > struct strbuf text = STRBUF_INIT; > struct strbuf pattern = STRBUF_INIT; > size_t prefix; > int ret = 0; > - const char *git_dir; > int already_tried_absolute = 0; > > - if (opts->git_dir) > - git_dir = opts->git_dir; > - else > + if (!path) > goto done; > > - strbuf_realpath(&text, git_dir, 1); > + strbuf_realpath(&text, path, 1); > strbuf_add(&pattern, cond, cond_len); > ret = prepare_include_condition_pattern(kvi, &pattern, &prefix); > if (ret < 0) > @@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi, > * which'll do the right thing > */ > strbuf_reset(&text); > - strbuf_add_absolute_path(&text, git_dir); > + strbuf_add_absolute_path(&text, path); > already_tried_absolute = 1; > goto again; > } > @@ -400,9 +397,15 @@ static int include_condition_is_true(const struct key_value_info *kvi, > const struct config_options *opts = inc->opts; > > if (skip_prefix_mem(cond, cond_len, "gitdir:", &cond, &cond_len)) > - return include_by_gitdir(kvi, opts, cond, cond_len, 0); > + return include_by_path(kvi, opts->git_dir, cond, cond_len, 0); > else if (skip_prefix_mem(cond, cond_len, "gitdir/i:", &cond, &cond_len)) > - return include_by_gitdir(kvi, opts, cond, cond_len, 1); > + return include_by_path(kvi, opts->git_dir, cond, cond_len, 1); > + else if (skip_prefix_mem(cond, cond_len, "worktree:", &cond, &cond_len)) > + return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL, > + cond, cond_len, 0); > + else if (skip_prefix_mem(cond, cond_len, "worktree/i:", &cond, &cond_len)) > + return include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL, > + cond, cond_len, 1); > else if (skip_prefix_mem(cond, cond_len, "onbranch:", &cond, &cond_len)) > return include_by_branch(inc, cond, cond_len); > else if (skip_prefix_mem(cond, cond_len, "hasconfig:remote.*.url:", &cond, ^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 2/3] Documentation/config: add includeIf "worktree" 2026-04-02 2:58 [PATCH v2 0/3] includeIf: add "worktree" condition for matching working tree path Chen Linxuan via B4 Relay 2026-04-02 2:58 ` [PATCH v2 1/3] config: add "worktree" and "worktree/i" includeIf conditions Chen Linxuan via B4 Relay @ 2026-04-02 2:58 ` Chen Linxuan via B4 Relay 2026-04-02 2:58 ` [PATCH v2 3/3] t1305: add tests for " Chen Linxuan via B4 Relay 2 siblings, 0 replies; 5+ messages in thread From: Chen Linxuan via B4 Relay @ 2026-04-02 2:58 UTC (permalink / raw) To: git; +Cc: Kristoffer Haugsbakk, Chen Linxuan From: Chen Linxuan <me@black-desk.cn> Add documentation for the newly introduced `worktree` and `worktree/i` conditional include keywords. Describe how they differ from `gitdir` (matching the working tree checkout path instead of the `.git` directory path), and list the supported pattern features: glob wildcards, `**/` and `/**`, `~` expansion, `./` relative paths, and trailing-`/` prefix matching. Note that the condition never matches in a bare repository. Also add usage examples alongside the existing `gitdir` examples. Signed-off-by: Chen Linxuan <me@black-desk.cn> --- Documentation/config.adoc | 50 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/Documentation/config.adoc b/Documentation/config.adoc index 62eebe7c5450..a4f3ec905098 100644 --- a/Documentation/config.adoc +++ b/Documentation/config.adoc @@ -146,6 +146,48 @@ refer to linkgit:gitignore[5] for details. For convenience: This is the same as `gitdir` except that matching is done case-insensitively (e.g. on case-insensitive file systems) +`worktree`:: + The data that follows the keyword `worktree` and a colon is used as a + glob pattern. If the working directory of the current worktree matches + the pattern, the include condition is met. ++ +The worktree location is the path where files are checked out (as returned +by `git rev-parse --show-toplevel`). This is different from `gitdir`, which +matches the `.git` directory path. In a linked worktree, the worktree path +is the directory where that worktree's files are located, not the main +repository's `.git` directory. ++ +The pattern can contain standard globbing wildcards and two additional +ones, `**/` and `/**`, that can match multiple path components. Please +refer to linkgit:gitignore[5] for details. For convenience: + + * If the pattern starts with `~/`, `~` will be substituted with the + content of the environment variable `HOME`. + + * If the pattern starts with `./`, it is replaced with the directory + containing the current config file. + + * If the pattern does not start with either `~/`, `./` or `/`, `**/` + will be automatically prepended. For example, the pattern `foo/bar` + becomes `**/foo/bar` and would match `/any/path/to/foo/bar`. + + * If the pattern ends with `/`, `**` will be automatically added. For + example, the pattern `foo/` becomes `foo/**`. In other words, it + matches "foo" and everything inside, recursively. ++ +This condition will never match in a bare repository (which has no worktree). ++ +This is useful when you need to use different `user.name`, `user.email`, or +GPG keys in different worktrees of the same repository. While +`extensions.worktreeConfig` also allows per-worktree configuration, it +requires changes inside each repository. This condition can be set in the +user's global configuration file (e.g. `~/.config/git/config`) and applies +to multiple repositories at once. + +`worktree/i`:: + This is the same as `worktree` except that matching is done + case-insensitively (e.g. on case-insensitive file systems) + `onbranch`:: The data that follows the keyword `onbranch` and a colon is taken to be a pattern with standard globbing wildcards and two additional @@ -244,6 +286,14 @@ Example [includeIf "gitdir:~/to/group/"] path = /path/to/foo.inc +; include if the worktree is at /path/to/project-build +[includeIf "worktree:/path/to/project-build"] + path = build-config.inc + +; include for all worktrees inside /path/to/group +[includeIf "worktree:/path/to/group/"] + path = group-config.inc + ; relative paths are always relative to the including ; file (if the condition is true); their location is not ; affected by the condition -- 2.53.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 3/3] t1305: add tests for includeIf "worktree" 2026-04-02 2:58 [PATCH v2 0/3] includeIf: add "worktree" condition for matching working tree path Chen Linxuan via B4 Relay 2026-04-02 2:58 ` [PATCH v2 1/3] config: add "worktree" and "worktree/i" includeIf conditions Chen Linxuan via B4 Relay 2026-04-02 2:58 ` [PATCH v2 2/3] Documentation/config: add includeIf "worktree" Chen Linxuan via B4 Relay @ 2026-04-02 2:58 ` Chen Linxuan via B4 Relay 2 siblings, 0 replies; 5+ messages in thread From: Chen Linxuan via B4 Relay @ 2026-04-02 2:58 UTC (permalink / raw) To: git; +Cc: Kristoffer Haugsbakk, Chen Linxuan From: Chen Linxuan <me@black-desk.cn> Cover the following scenarios unique to the "worktree" condition (path matching features such as glob, tilde, icase are already exercised by the gitdir tests): - bare repository (condition must not match) - multiple worktrees: main and linked worktrees each match their own path-based condition while sharing a single config file; a third linked worktree verifies directory-prefix matching with a trailing slash - symlinked worktree: the path is resolved before matching Signed-off-by: Chen Linxuan <me@black-desk.cn> --- t/t1305-config-include.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh index 6e51f892f320..8a5ba4b884d3 100755 --- a/t/t1305-config-include.sh +++ b/t/t1305-config-include.sh @@ -396,4 +396,70 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di test_must_fail nongit git --git-dir=nonexistent config get foo.bar ' +# worktree: conditional include tests + +test_expect_success 'conditional include, worktree bare repo' ' + git init --bare wt-bare && + ( + cd wt-bare && + echo "[includeIf \"worktree:/\"]path=bar-bare" >>config && + echo "[test]wtbare=1" >bar-bare && + test_must_fail git config test.wtbare + ) +' + +test_expect_success 'conditional include, worktree multiple worktrees' ' + git init wt-multi && + ( + cd wt-multi && + test_commit initial && + git worktree add -b linked-branch ../wt-linked HEAD && + git worktree add -b prefix-branch ../wt-prefix/linked HEAD + ) && + wt_main="$(cd wt-multi && pwd)" && + wt_linked="$(cd wt-linked && pwd)" && + wt_prefix_parent="$(cd wt-prefix && pwd)" && + cat >>wt-multi/.git/config <<-EOF && + [includeIf "worktree:$wt_main"] + path = main-config + [includeIf "worktree:$wt_linked"] + path = linked-config + [includeIf "worktree:$wt_prefix_parent/"] + path = prefix-config + EOF + echo "[test]mainvar=main" >wt-multi/.git/main-config && + echo "[test]linkedvar=linked" >wt-multi/.git/linked-config && + echo "[test]prefixvar=prefix" >wt-multi/.git/prefix-config && + echo main >expect && + git -C wt-multi config test.mainvar >actual && + test_cmp expect actual && + test_must_fail git -C wt-multi config test.linkedvar && + test_must_fail git -C wt-multi config test.prefixvar && + echo linked >expect && + git -C wt-linked config test.linkedvar >actual && + test_cmp expect actual && + test_must_fail git -C wt-linked config test.mainvar && + test_must_fail git -C wt-linked config test.prefixvar && + echo prefix >expect && + git -C wt-prefix/linked config test.prefixvar >actual && + test_cmp expect actual && + test_must_fail git -C wt-prefix/linked config test.mainvar && + test_must_fail git -C wt-prefix/linked config test.linkedvar +' + +test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' ' + mkdir real-wt && + ln -s real-wt link-wt && + git init link-wt/repo && + ( + cd link-wt/repo && + # repo->worktree resolves symlinks, so use real path in pattern + echo "[includeIf \"worktree:**/real-wt/repo\"]path=bar-link" >>.git/config && + echo "[test]wtlink=2" >.git/bar-link && + echo 2 >expect && + git config test.wtlink >actual && + test_cmp expect actual + ) +' + test_done -- 2.53.0 ^ permalink raw reply related [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-04-02 21:22 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2026-04-02 2:58 [PATCH v2 0/3] includeIf: add "worktree" condition for matching working tree path Chen Linxuan via B4 Relay 2026-04-02 2:58 ` [PATCH v2 1/3] config: add "worktree" and "worktree/i" includeIf conditions Chen Linxuan via B4 Relay 2026-04-02 21:22 ` Junio C Hamano 2026-04-02 2:58 ` [PATCH v2 2/3] Documentation/config: add includeIf "worktree" Chen Linxuan via B4 Relay 2026-04-02 2:58 ` [PATCH v2 3/3] t1305: add tests for " Chen Linxuan via B4 Relay
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox