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

From: Patrick Steinhardt <[email-masked]>
Date: Tue, 12 May 2026 09:14:03 +0200

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.

> 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