Subject: Re: [PATCH v3 2/2] config: add "worktree" and "worktree/i" includeIf conditions

From: Phillip Wood <[email-masked]>
Date: Tue, 12 May 2026 16:07:12 +0100

On 12/05/2026 08:14, Patrick Steinhardt wrote:
> On Fri, Apr 03, 2026 at 03:02:29PM +0800, Chen Linxuan via B4 Relay wrote:
>> From: Chen Linxuan <[email-masked]>
>>
>> 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.
> 
> Seems sensible.

We already support per-worktree config settings via 
extensions.worktreeConfig, so it would be helpful to explain why it is 
more convenient to set the config based on the worktree's path, rather 
than just running "git config --worktree" inside the worktree. Do you 
have multiple repositories with worktrees checked out under a common 
prefix that you want to share the same config setting?

Thanks

Phillip

>> The implementation reuses the include_by_path() helper introduced in
>> the previous commit, passing the worktree path in place of the
>> gitdir.  The condition never matches in bare repositories (where
>> there is no worktree) or during early config reading (where no
>> repository is available).
> 
> Right. This is because `repo_get_work_tree()` would return a NULL
> pointer in these cases, and `include_by_path()` exits early in that
> case.
> 
>> 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 whole listing here is the exact same as we have for the `gitdir`
> condition. Can we maybe deduplicate these into a common section?
> 
>> diff --git a/config.c b/config.c
>> index 7d5dae0e8450..6d0c2d0725e4 100644
>> --- a/config.c
>> +++ b/config.c
>> @@ -400,6 +400,12 @@ static int include_condition_is_true(const struct key_value_info *kvi,
>>   		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_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,
> 
> I feel like this is something that we might eventually want to convert
> to be table-driven. But I think that doesn't have to happen as part of
> this patch series.
> 
>> 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
> 
> Just because it was explicitly mentioned: we might also want to have a
> test that verifies this works with early-config parsing. We already have
> a similar test for "gitdir:" in "conditional include, early config
> reading".
> 
> And should we also have a "nongit" branch where we verify outside a
> repository?
> 
> Other than that this series looks good to me, thanks!
> 
> Patrick
>