git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v8 0/2] includeIf: add "worktree" condition for matching working tree path

From
Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>
Date
Jul 10, 2026, 06:43 UTC
Message-ID
<20260710-includeif-worktree-v8-0-04686d8a616c@black-desk.cn>
In-Reply-To
<20260709-includeif-worktree-v7-0-e87e705e8df6@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 working directory of the current
    worktree 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 v8:
- Drop the v7 symlink-preserving worktree path implementation.  Patrick
  pointed out that the setup-side plumbing was too invasive and likely to
  conflict with the ongoing setup discovery work.
- Document the current limitation instead: includeIf "worktree:" matches
  the realpath-resolved worktree location, so symlink spellings may not
  match.
- Return the series to two patches, based on v6 plus the documentation
  update.
- Link to v7: https://lore.kernel.org/r/20260709-includeif-worktree-v7-0-e87e705e8df6@black-desk.cn
Changes in v7:
- Preserve the symlinked spelling of the worktree path and match
  includeIf "worktree:" against it, so the condition now matches both
  the symlinked and the real path, consistent with "gitdir:"
  (Patrick Steinhardt, v6 review).
- Split the work into a preparatory commit that stores a non-realpath
  worktree path and a follow-up that wires it into includeIf.
- Extend symlink test coverage to subdirectories and linked worktrees.
- Link to v6: https://lore.kernel.org/r/20260703-includeif-worktree-v6-0-a13893ad9a7f@black-desk.cn
Changes in v6:
- Rebase onto current `master` at Git 2.55.
- Add an in-code comment explaining why the non-repository worktree
  tests use the loose `**.path` pattern (suggested by Junio C Hamano).
- Link to v5: https://lore.kernel.org/r/20260525-includeif-worktree-v5-0-1efe525d025a@black-desk.cn
Changes in v5:
- Fix Windows CI failure: use `**` glob pattern instead of `/` in the
  "worktree without repository" tests, since `/` as a path pattern is
  Unix-specific and does not match Windows paths.
  Github CI pass: https://github.com/black-desk/git/actions/runs/26380466288
- Add a test verifying case-sensitive matching by default, with the
  `!CASE_INSENSITIVE_FS` prerequisite (suggested by Patrick Steinhardt).
- Link to v4: https://lore.kernel.org/r/20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn
Changes in v4:
- Deduplicate the worktree pattern documentation by referencing the
  gitdir syntax instead of repeating the full pattern description
  (suggested by Patrick Steinhardt).
- Add documentation comparing includeIf "worktree:" with
  extensions.worktreeConfig, including a concrete use case example
  (suggested by Phillip Wood, Junio C Hamano).
- Add a test verifying that the worktree condition does not match
  during early config reading (suggested by Patrick Steinhardt).
- Add tests for the non-repository (nongit) scenario (suggested by
  Patrick Steinhardt).
- Add a test for the case-insensitive "worktree/i" variant
- Link to v3: https://lore.kernel.org/r/20260403-includeif-worktree-v3-0-109ce5782b03@black-desk.cn
Changes in v3:
- Apply Junio's suggestion.
- Link to v2: https://lore.kernel.org/r/20260402-includeif-worktree-v2-0-36e339b898d7@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 (2):
      config: refactor include_by_gitdir() into include_by_path()
      config: add "worktree" and "worktree/i" includeIf conditions
 Documentation/config.adoc |  53 +++++++++++++++++++
 config.c                  |  25 +++++----
 t/t1305-config-include.sh | 128 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 195 insertions(+), 11 deletions(-)
Range-diff versus v7:
1:  510f28d207f8 = 1:  731d928b1dfa config: refactor include_by_gitdir() into include_by_path()
2:  0f83ffee0338 < -:  ------------ repository: keep a symlink-preserving copy of the worktree path
3:  18d1abc325fc ! 2:  56c792090625 config: add "worktree" and "worktree/i" includeIf conditions
    @@ Commit message
     
         Introduce two new condition keywords:
     
    -      - worktree:<pattern> matches the working directory of the current
    -        worktree (the path returned by git rev-parse --show-toplevel)
    -        against a glob pattern.
    +      - 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 reuses the include_by_path() helper, passing
    -    repo_get_work_tree_original() (added in the previous commit; it keeps
    -    the symlink-preserving spelling of the worktree path) in place of the
    -    gitdir.  As with gitdir, include_by_path() then matches both the
    -    realpath and the original spelling, so a pattern may use either.  The
    -    condition never matches in bare repositories (where there is no
    -    worktree) or during early config reading (where no repository is
    -    available).
    +    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).
     
         Add documentation describing the new conditions, including a comparison
    -    with extensions.worktreeConfig.  Add tests covering bare repositories,
    -    multiple worktrees, symlinked and subdir-of-symlinked worktree paths,
    -    case-sensitive and case-insensitive matching, early config reading,
    +    with extensions.worktreeConfig and a note that worktree matching currently
    +    uses the realpath-resolved worktree location.  Add tests covering bare
    +    repositories, multiple worktrees, realpath-resolved symlinked worktree
    +    paths, case-sensitive and case-insensitive matching, early config reading,
         and non-repository scenarios.
     
         Signed-off-by: Chen Linxuan <me@black-desk.cn>
    @@ Documentation/config.adoc: refer to linkgit:gitignore[5] for details. For conven
     +`**/`, and trailing-`/` prefix matching). This condition will never match
     +in a bare repository (which has no worktree).
     ++
    ++Unlike `gitdir`, the `worktree` condition currently matches only the
    ++realpath-resolved worktree location. If the working tree was entered via a
    ++symbolic link, a pattern that uses the symbolic-link spelling may not match;
    ++use the real path instead.
    +++
     +This is useful when you want to apply configuration based on where the
     +working tree is located on the filesystem. For example, a contributor who
     +works on the same project both personally and as an employee can use
    @@ config.c: static int include_condition_is_true(const struct key_value_info *kvi,
      	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_original(inc->repo) : NULL,
    ++		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_original(inc->repo) : NULL,
    ++		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);
    @@ t/t1305-config-include.sh: test_expect_success 'onbranch without repository but
     +	test_must_fail git -C wt-prefix/linked config test.linkedvar
     +'
     +
    -+test_expect_success SYMLINKS 'conditional include, worktree matching symlink' '
    -+	mkdir sym-real &&
    -+	ln -s sym-real sym-link &&
    -+	git init sym-link/repo &&
    -+	(
    -+		cd sym-link/repo &&
    -+		link_path="$(pwd)" &&
    -+		real_path="$(test-tool path-utils real_path "$link_path")" &&
    -+		cat >>.git/config <<-EOF &&
    -+		[includeIf "gitdir:$link_path/.git"]
    -+			path = gitdir-link
    -+		[includeIf "gitdir:$real_path/.git"]
    -+			path = gitdir-real
    -+		[includeIf "worktree:$link_path"]
    -+			path = worktree-link
    -+		[includeIf "worktree:$real_path"]
    -+			path = worktree-real
    -+		EOF
    -+		echo "[test]gitdirlink=1" >.git/gitdir-link &&
    -+		echo "[test]gitdirreal=1" >.git/gitdir-real &&
    -+		echo "[test]worktreelink=1" >.git/worktree-link &&
    -+		echo "[test]worktreereal=1" >.git/worktree-real &&
    -+		git config get test.gitdirlink &&
    -+		git config get test.gitdirreal &&
    -+		git config get test.worktreelink &&
    -+		git config get test.worktreereal &&
    -+		# from a subdirectory, the logical worktree path is recovered by
    -+		# stripping the below-root suffix, so both spellings still match
    -+		mkdir d &&
    -+		cd d &&
    -+		git config get test.worktreelink &&
    -+		git config get test.worktreereal
    -+	)
    -+'
    -+
    -+test_expect_success SYMLINKS 'conditional include, worktree matching symlink of a linked worktree' '
    -+	git init wt-main &&
    -+	( cd wt-main && test_commit initial ) &&
    -+	git -C wt-main worktree add --detach ../wt-real &&
    -+	ln -s wt-real wt-link &&
    -+	wt_main="$(cd wt-main && pwd)" &&
    ++test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '
    ++	mkdir real-wt &&
    ++	ln -s real-wt link-wt &&
    ++	git init link-wt/repo &&
     +	(
    -+		cd wt-link &&
    -+		link_path="$(pwd)" &&
    -+		real_path="$(test-tool path-utils real_path "$link_path")" &&
    -+		cat >>"$wt_main/.git/config" <<-EOF &&
    -+		[includeIf "worktree:$link_path"]
    -+			path = wt-link
    -+		[includeIf "worktree:$real_path"]
    -+			path = wt-real
    -+		EOF
    -+		echo "[test]wtlink=1" >"$wt_main/.git/wt-link" &&
    -+		echo "[test]wtreal=1" >"$wt_main/.git/wt-real" &&
    -+		test "$(git config get test.wtlink)" = "1" &&
    -+		test "$(git config get test.wtreal)" = "1"
    ++		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
     +	)
     +'
     +

--- base-commit: f85a7e662054a7b0d9070e432508831afa214b47

Previous: Chen Linxuan via B4 RelayNext: Patrick Steinhardt
Message 3 of 4 in “config: refactor include_by_gitdir() into include_by_path()”
  1. 1/2 config: refactor include_by_gitdir() into include_by_path()Chen Linxuan via B4 Relay, Jul 10, 2026
  2. 2/2 config: add "worktree" and "worktree/i" includeIf conditionsChen Linxuan via B4 Relay, Jul 10, 2026
  3. 0/2 includeIf: add "worktree" condition for matching working tree pathChen Linxuan via B4 Relay, Jul 10, 2026
  4. Patrick SteinhardtJul 13, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.