{"thread":{"id":"65629","subject":"[PATCH v4 0/2] includeIf: add \"worktree\" condition for matching working tree path","startedAt":"2026-05-13T08:08:56Z","lastAt":"2026-05-25T03:01:39Z","messageCount":8,"participants":["Chen Linxuan via B4 Relay","Phillip Wood","Patrick Steinhardt","Junio C Hamano","Chen Linxuan"],"isPatch":true,"patchVersion":4,"patchTotal":2},"messages":[{"id":"543234","messageId":"20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn","threadId":"65629","inReplyTo":null,"subject":"[PATCH v4 0/2] includeIf: add \"worktree\" condition for matching working tree path","fromName":"Chen Linxuan via B4 Relay","fromEmail":"devnull+me.black-desk.cn@kernel.org","sentAt":"2026-05-13T08:08:16Z","receivedAt":"2026-05-13T08:08:56Z","isPatch":true,"body":"The `includeIf` mechanism already supports matching on the `.git`\ndirectory path (`gitdir`) and the currently checked out branch\n(`onbranch`).  But in multi-worktree setups the `.git` directory of a\nlinked worktree points into the main repository's `.git/worktrees/`\narea, which makes `gitdir` patterns cumbersome when one wants to\ninclude config based on the working tree's checkout path instead.\n\nIntroduce two new condition keywords:\n\n  - `worktree:<pattern>` matches the realpath of the current worktree's\n    working directory against a glob pattern.\n  - `worktree/i:<pattern>` is the case-insensitive variant.\n\nSupported pattern features: glob wildcards, `**/` and `/**`, `~`\nexpansion, `./` relative paths, and trailing-`/` prefix matching.\nThe condition never matches in a bare repository.\n\nSigned-off-by: Chen Linxuan <me@black-desk.cn>\n---\nChanges in v4:\n- Deduplicate the worktree pattern documentation by referencing the\n  gitdir syntax instead of repeating the full pattern description\n  (suggested by Patrick Steinhardt).\n- Add documentation comparing includeIf \"worktree:\" with\n  extensions.worktreeConfig, including a concrete use case example\n  (suggested by Phillip Wood, Junio C Hamano).\n- Add a test verifying that the worktree condition does not match\n  during early config reading (suggested by Patrick Steinhardt).\n- Add tests for the non-repository (nongit) scenario (suggested by\n  Patrick Steinhardt).\n- Add a test for the case-insensitive \"worktree/i\" variant\n- Link to v3: https://lore.kernel.org/r/20260403-includeif-worktree-v3-0-109ce5782b03@black-desk.cn\n\nChanges in v3:\n- Apply Junio's suggestion.\n- Link to v2: https://lore.kernel.org/r/20260402-includeif-worktree-v2-0-36e339b898d7@black-desk.cn\n\nChanges in v2:\n\n- Add missing signed-off-by lines.\n- Link to v1: https://lore.kernel.org/r/20260401-includeif-worktree-v1-0-906db69f2c79@black-desk.cn\n\n---\nChen Linxuan (2):\n      config: refactor include_by_gitdir() into include_by_path()\n      config: add \"worktree\" and \"worktree/i\" includeIf conditions\n\n Documentation/config.adoc |  48 ++++++++++++++++++++\n config.c                  |  25 +++++-----\n t/t1305-config-include.sh | 113 ++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 175 insertions(+), 11 deletions(-)\n---\nbase-commit: 270e10ad6dda3379ea0da7efd11e4fbf2cd7a325\nchange-id: 20260401-includeif-worktree-fcb64950dfba\n\nBest regards,\n-- \nChen Linxuan <me@black-desk.cn>\n\n\n"},{"id":"543235","messageId":"20260513-includeif-worktree-v4-2-f8e6212d1fba@black-desk.cn","threadId":"65629","inReplyTo":"20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn","subject":"[PATCH v4 2/2] config: add \"worktree\" and \"worktree/i\" includeIf conditions","fromName":"Chen Linxuan via B4 Relay","fromEmail":"devnull+me.black-desk.cn@kernel.org","sentAt":"2026-05-13T08:08:18Z","receivedAt":"2026-05-13T08:08:56Z","isPatch":true,"body":"From: Chen Linxuan <me@black-desk.cn>\n\nThe includeIf mechanism already supports matching on the .git\ndirectory path (gitdir) and the currently checked out branch\n(onbranch).  But in multi-worktree setups the .git directory of a\nlinked worktree points into the main repository's .git/worktrees/\narea, which makes gitdir patterns cumbersome when one wants to\ninclude config based on the working tree's checkout path instead.\n\nIntroduce two new condition keywords:\n\n  - worktree:<pattern> matches the realpath of the current worktree's\n    working directory (i.e. repo_get_work_tree()) against a glob\n    pattern.  This is the path returned by git rev-parse\n    --show-toplevel.\n\n  - worktree/i:<pattern> is the case-insensitive variant.\n\nThe implementation reuses the include_by_path() helper introduced in\nthe previous commit, passing the worktree path in place of the\ngitdir.  The condition never matches in bare repositories (where\nthere is no worktree) or during early config reading (where no\nrepository is available).\n\nAdd documentation describing the new conditions, including a comparison\nwith extensions.worktreeConfig.  Add tests covering bare repositories,\nmultiple worktrees, symlinked worktree paths, case-insensitive matching,\nearly config reading, and non-repository scenarios.\n\nSigned-off-by: Chen Linxuan <me@black-desk.cn>\n---\n Documentation/config.adoc |  48 ++++++++++++++++++++\n config.c                  |   6 +++\n t/t1305-config-include.sh | 113 ++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 167 insertions(+)\n\ndiff --git a/Documentation/config.adoc b/Documentation/config.adoc\nindex 62eebe7c5450..6299b1e3a019 100644\n--- a/Documentation/config.adoc\n+++ b/Documentation/config.adoc\n@@ -146,6 +146,46 @@ refer to linkgit:gitignore[5] for details. For convenience:\n \tThis is the same as `gitdir` except that matching is done\n \tcase-insensitively (e.g. on case-insensitive file systems)\n \n+`worktree`::\n+\tThe data that follows the keyword `worktree` and a colon is used as a\n+\tglob pattern. If the working directory of the current worktree matches\n+\tthe pattern, the include condition is met.\n++\n+The worktree location is the path where files are checked out (as returned\n+by `git rev-parse --show-toplevel`). This is different from `gitdir`, which\n+matches the `.git` directory path. In a linked worktree, the worktree path\n+is the directory where that worktree's files are located, not the main\n+repository's `.git` directory.\n++\n+The pattern uses the same glob syntax as `gitdir` (including `~/`, `./`,\n+`**/`, and trailing-`/` prefix matching). This condition will never match\n+in a bare repository (which has no worktree).\n++\n+This is useful when you want to apply configuration based on where the\n+working tree is located on the filesystem. For example, a contributor who\n+works on the same project both personally and as an employee can use\n+different `user.name` and `user.email` values depending on which directory\n+the worktree is checked out under:\n++\n+----\n+[includeIf \"worktree:/home/user/work/\"]\n+    path = ~/.config/git/work.inc\n+[includeIf \"worktree:/home/user/personal/\"]\n+    path = ~/.config/git/personal.inc\n+----\n++\n+While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports\n+per-worktree configuration, it stores the config inside each repository's\n+`.git/config.worktree` file and requires running `git config --worktree`\n+inside each worktree individually. In contrast, `includeIf \"worktree:...\"`\n+can be set once in a global or system-level configuration file (e.g.\n+`~/.config/git/config`) and applies to all repositories at once based on\n+their worktree location.\n+\n+`worktree/i`::\n+\tThis is the same as `worktree` except that matching is done\n+\tcase-insensitively (e.g. on case-insensitive file systems)\n+\n `onbranch`::\n \tThe data that follows the keyword `onbranch` and a colon is taken to be a\n \tpattern with standard globbing wildcards and two additional\n@@ -244,6 +284,14 @@ Example\n [includeIf \"gitdir:~/to/group/\"]\n \tpath = /path/to/foo.inc\n \n+; include if the worktree is at /path/to/project-build\n+[includeIf \"worktree:/path/to/project-build\"]\n+\tpath = build-config.inc\n+\n+; include for all worktrees inside /path/to/group\n+[includeIf \"worktree:/path/to/group/\"]\n+\tpath = group-config.inc\n+\n ; relative paths are always relative to the including\n ; file (if the condition is true); their location is not\n ; affected by the condition\ndiff --git a/config.c b/config.c\nindex 7d5dae0e8450..6d0c2d0725e4 100644\n--- a/config.c\n+++ b/config.c\n@@ -400,6 +400,12 @@ static int include_condition_is_true(const struct key_value_info *kvi,\n \t\treturn include_by_path(kvi, opts->git_dir, cond, cond_len, 0);\n \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n \t\treturn include_by_path(kvi, opts->git_dir, cond, cond_len, 1);\n+\telse if (skip_prefix_mem(cond, cond_len, \"worktree:\", &cond, &cond_len))\n+\t\treturn include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,\n+\t\t\t\t       cond, cond_len, 0);\n+\telse if (skip_prefix_mem(cond, cond_len, \"worktree/i:\", &cond, &cond_len))\n+\t\treturn include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,\n+\t\t\t\t       cond, cond_len, 1);\n \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n \t\treturn include_by_branch(inc, cond, cond_len);\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\ndiff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh\nindex 6e51f892f320..07b6fb649cd2 100755\n--- a/t/t1305-config-include.sh\n+++ b/t/t1305-config-include.sh\n@@ -396,4 +396,117 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di\n \ttest_must_fail nongit git --git-dir=nonexistent config get foo.bar\n '\n \n+# worktree: conditional include tests\n+\n+test_expect_success 'conditional include, worktree bare repo' '\n+\tgit init --bare wt-bare &&\n+\t(\n+\t\tcd wt-bare &&\n+\t\techo \"[includeIf \\\"worktree:/\\\"]path=bar-bare\" >>config &&\n+\t\techo \"[test]wtbare=1\" >bar-bare &&\n+\t\ttest_must_fail git config test.wtbare\n+\t)\n+'\n+\n+test_expect_success 'conditional include, worktree multiple worktrees' '\n+\tgit init wt-multi &&\n+\t(\n+\t\tcd wt-multi &&\n+\t\ttest_commit initial &&\n+\t\tgit worktree add -b linked-branch ../wt-linked HEAD &&\n+\t\tgit worktree add -b prefix-branch ../wt-prefix/linked HEAD\n+\t) &&\n+\twt_main=\"$(cd wt-multi && pwd)\" &&\n+\twt_linked=\"$(cd wt-linked && pwd)\" &&\n+\twt_prefix_parent=\"$(cd wt-prefix && pwd)\" &&\n+\tcat >>wt-multi/.git/config <<-EOF &&\n+\t[includeIf \"worktree:$wt_main\"]\n+\t\tpath = main-config\n+\t[includeIf \"worktree:$wt_linked\"]\n+\t\tpath = linked-config\n+\t[includeIf \"worktree:$wt_prefix_parent/\"]\n+\t\tpath = prefix-config\n+\tEOF\n+\techo \"[test]mainvar=main\" >wt-multi/.git/main-config &&\n+\techo \"[test]linkedvar=linked\" >wt-multi/.git/linked-config &&\n+\techo \"[test]prefixvar=prefix\" >wt-multi/.git/prefix-config &&\n+\techo main >expect &&\n+\tgit -C wt-multi config test.mainvar >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git -C wt-multi config test.linkedvar &&\n+\ttest_must_fail git -C wt-multi config test.prefixvar &&\n+\techo linked >expect &&\n+\tgit -C wt-linked config test.linkedvar >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git -C wt-linked config test.mainvar &&\n+\ttest_must_fail git -C wt-linked config test.prefixvar &&\n+\techo prefix >expect &&\n+\tgit -C wt-prefix/linked config test.prefixvar >actual &&\n+\ttest_cmp expect actual &&\n+\ttest_must_fail git -C wt-prefix/linked config test.mainvar &&\n+\ttest_must_fail git -C wt-prefix/linked config test.linkedvar\n+'\n+\n+test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '\n+\tmkdir real-wt &&\n+\tln -s real-wt link-wt &&\n+\tgit init link-wt/repo &&\n+\t(\n+\t\tcd link-wt/repo &&\n+\t\t# repo->worktree resolves symlinks, so use real path in pattern\n+\t\techo \"[includeIf \\\"worktree:**/real-wt/repo\\\"]path=bar-link\" >>.git/config &&\n+\t\techo \"[test]wtlink=2\" >.git/bar-link &&\n+\t\techo 2 >expect &&\n+\t\tgit config test.wtlink >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+test_expect_success 'conditional include, worktree, icase' '\n+\tgit init wt-icase &&\n+\t(\n+\t\tcd wt-icase &&\n+\t\ttest_commit initial &&\n+\t\twt_path=\"$(pwd)\" &&\n+\t\twt_upper=$(echo \"$wt_path\" | tr a-z A-Z) &&\n+\t\techo \"[includeIf \\\"worktree/i:$wt_upper\\\"]path=icase-inc\" >>.git/config &&\n+\t\techo \"[test]wticase=1\" >.git/icase-inc &&\n+\t\techo 1 >expect &&\n+\t\tgit config test.wticase >actual &&\n+\t\ttest_cmp expect actual\n+\t)\n+'\n+\n+# The \"worktree\" condition cannot match during early config reading\n+# because the repository object is not yet fully initialized and\n+# repo_get_work_tree() returns NULL.\n+test_expect_success 'conditional include, worktree does not match in early config' '\n+\tgit init wt-early &&\n+\t(\n+\t\tcd wt-early &&\n+\t\ttest_commit initial &&\n+\t\twt_path=\"$(pwd)\" &&\n+\t\techo \"[includeIf \\\"worktree:$wt_path\\\"]path=early-inc\" >>.git/config &&\n+\t\techo \"[test]wtearly=1\" >.git/early-inc &&\n+\t\ttest-tool config read_early_config test.wtearly >actual &&\n+\t\ttest_must_be_empty actual\n+\t)\n+'\n+\n+test_expect_success 'conditional include, worktree without repository' '\n+\ttest_when_finished \"rm -f .gitconfig config.inc\" &&\n+\tgit config set -f .gitconfig \"includeIf.worktree:/.path\" config.inc &&\n+\tgit config set -f config.inc foo.bar baz &&\n+\tgit config get foo.bar &&\n+\ttest_must_fail nongit git config get foo.bar\n+'\n+\n+test_expect_success 'conditional include, worktree without repository but explicit nonexistent Git directory' '\n+\ttest_when_finished \"rm -f .gitconfig config.inc\" &&\n+\tgit config set -f .gitconfig \"includeIf.worktree:/.path\" config.inc &&\n+\tgit config set -f config.inc foo.bar baz &&\n+\tgit config get foo.bar &&\n+\ttest_must_fail nongit git --git-dir=nonexistent config get foo.bar\n+'\n+\n test_done\n\n-- \n2.53.0\n\n\n"},{"id":"543236","messageId":"20260513-includeif-worktree-v4-1-f8e6212d1fba@black-desk.cn","threadId":"65629","inReplyTo":"20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn","subject":"[PATCH v4 1/2] config: refactor include_by_gitdir() into include_by_path()","fromName":"Chen Linxuan via B4 Relay","fromEmail":"devnull+me.black-desk.cn@kernel.org","sentAt":"2026-05-13T08:08:17Z","receivedAt":"2026-05-13T08:08:56Z","isPatch":true,"body":"From: Chen Linxuan <me@black-desk.cn>\n\nThe include_by_gitdir() function matches the realpath of a given\npath against a glob pattern, but its interface is tightly coupled to\nthe gitdir condition: it takes a struct config_options *opts and\nextracts opts->git_dir internally.\n\nRefactor it into a more generic include_by_path() helper that takes\na const char *path parameter directly, and update the gitdir and\ngitdir/i callers to pass opts->git_dir explicitly.  No behavior\nchange, just preparing for the addition of a new worktree condition\nthat will reuse the same path-matching logic with a different path.\n\nSigned-off-by: Chen Linxuan <me@black-desk.cn>\n---\n config.c | 19 ++++++++-----------\n 1 file changed, 8 insertions(+), 11 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 156f2a24fa00..7d5dae0e8450 100644\n--- a/config.c\n+++ b/config.c\n@@ -235,23 +235,20 @@ static int prepare_include_condition_pattern(const struct key_value_info *kvi,\n \treturn 0;\n }\n \n-static int include_by_gitdir(const struct key_value_info *kvi,\n-\t\t\t     const struct config_options *opts,\n-\t\t\t     const char *cond, size_t cond_len, int icase)\n+static int include_by_path(const struct key_value_info *kvi,\n+\t\t\t   const char *path,\n+\t\t\t   const char *cond, size_t cond_len, int icase)\n {\n \tstruct strbuf text = STRBUF_INIT;\n \tstruct strbuf pattern = STRBUF_INIT;\n \tsize_t prefix;\n \tint ret = 0;\n-\tconst char *git_dir;\n \tint already_tried_absolute = 0;\n \n-\tif (opts->git_dir)\n-\t\tgit_dir = opts->git_dir;\n-\telse\n+\tif (!path)\n \t\tgoto done;\n \n-\tstrbuf_realpath(&text, git_dir, 1);\n+\tstrbuf_realpath(&text, path, 1);\n \tstrbuf_add(&pattern, cond, cond_len);\n \tret = prepare_include_condition_pattern(kvi, &pattern, &prefix);\n \tif (ret < 0)\n@@ -284,7 +281,7 @@ static int include_by_gitdir(const struct key_value_info *kvi,\n \t\t * which'll do the right thing\n \t\t */\n \t\tstrbuf_reset(&text);\n-\t\tstrbuf_add_absolute_path(&text, git_dir);\n+\t\tstrbuf_add_absolute_path(&text, path);\n \t\talready_tried_absolute = 1;\n \t\tgoto again;\n \t}\n@@ -400,9 +397,9 @@ static int include_condition_is_true(const struct key_value_info *kvi,\n \tconst struct config_options *opts = inc->opts;\n \n \tif (skip_prefix_mem(cond, cond_len, \"gitdir:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 0);\n+\t\treturn include_by_path(kvi, opts->git_dir, cond, cond_len, 0);\n \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n-\t\treturn include_by_gitdir(kvi, opts, cond, cond_len, 1);\n+\t\treturn include_by_path(kvi, opts->git_dir, cond, cond_len, 1);\n \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n \t\treturn include_by_branch(inc, cond, cond_len);\n \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n\n-- \n2.53.0\n\n\n"},{"id":"543242","messageId":"2989eb07-2933-4b5a-9e5c-33ef9b805528@gmail.com","threadId":"65629","inReplyTo":"20260513-includeif-worktree-v4-2-f8e6212d1fba@black-desk.cn","subject":"Re: [PATCH v4 2/2] config: add \"worktree\" and \"worktree/i\" includeIf conditions","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-13T13:59:00Z","receivedAt":"2026-05-13T13:59:06Z","isPatch":true,"body":"On 13/05/2026 09:08, Chen Linxuan via B4 Relay wrote:\n> From: Chen Linxuan <me@black-desk.cn>\n> \n> diff --git a/Documentation/config.adoc b/Documentation/config.adoc\n> index 62eebe7c5450..6299b1e3a019 100644\n> --- a/Documentation/config.adoc\n> +++ b/Documentation/config.adoc\n> @@ -146,6 +146,46 @@ refer to linkgit:gitignore[5] for details. For convenience:\n>   \tThis is the same as `gitdir` except that matching is done\n>   \tcase-insensitively (e.g. on case-insensitive file systems)\n>   \n> +`worktree`::\n> +\tThe data that follows the keyword `worktree` and a colon is used as a\n> +\tglob pattern. If the working directory of the current worktree matches\n> +\tthe pattern, the include condition is met.\n> ++\n> +The worktree location is the path where files are checked out (as returned\n> +by `git rev-parse --show-toplevel`). This is different from `gitdir`, which\n> +matches the `.git` directory path. In a linked worktree, the worktree path\n> +is the directory where that worktree's files are located, not the main\n> +repository's `.git` directory.\n> ++\n> +The pattern uses the same glob syntax as `gitdir` (including `~/`, `./`,\n> +`**/`, and trailing-`/` prefix matching). This condition will never match\n> +in a bare repository (which has no worktree).\n> ++\n> +This is useful when you want to apply configuration based on where the\n> +working tree is located on the filesystem. For example, a contributor who\n> +works on the same project both personally and as an employee can use\n> +different `user.name` and `user.email` values depending on which directory\n> +the worktree is checked out under:\n> ++\n> +----\n> +[includeIf \"worktree:/home/user/work/\"]\n> +    path = ~/.config/git/work.inc\n> +[includeIf \"worktree:/home/user/personal/\"]\n> +    path = ~/.config/git/personal.inc\n> +----\n> ++\n> +While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports\n> +per-worktree configuration, it stores the config inside each repository's\n> +`.git/config.worktree` file and requires running `git config --worktree`\n> +inside each worktree individually. In contrast, `includeIf \"worktree:...\"`\n> +can be set once in a global or system-level configuration file (e.g.\n> +`~/.config/git/config`) and applies to all repositories at once based on\n> +their worktree location.\n\nThanks for expanding the documentation - this looks good to me.\n\nPhillip\n\n> +`worktree/i`::\n> +\tThis is the same as `worktree` except that matching is done\n> +\tcase-insensitively (e.g. on case-insensitive file systems)\n> +\n>   `onbranch`::\n>   \tThe data that follows the keyword `onbranch` and a colon is taken to be a\n>   \tpattern with standard globbing wildcards and two additional\n> @@ -244,6 +284,14 @@ Example\n>   [includeIf \"gitdir:~/to/group/\"]\n>   \tpath = /path/to/foo.inc\n>   \n> +; include if the worktree is at /path/to/project-build\n> +[includeIf \"worktree:/path/to/project-build\"]\n> +\tpath = build-config.inc\n> +\n> +; include for all worktrees inside /path/to/group\n> +[includeIf \"worktree:/path/to/group/\"]\n> +\tpath = group-config.inc\n> +\n>   ; relative paths are always relative to the including\n>   ; file (if the condition is true); their location is not\n>   ; affected by the condition\n> diff --git a/config.c b/config.c\n> index 7d5dae0e8450..6d0c2d0725e4 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -400,6 +400,12 @@ static int include_condition_is_true(const struct key_value_info *kvi,\n>   \t\treturn include_by_path(kvi, opts->git_dir, cond, cond_len, 0);\n>   \telse if (skip_prefix_mem(cond, cond_len, \"gitdir/i:\", &cond, &cond_len))\n>   \t\treturn include_by_path(kvi, opts->git_dir, cond, cond_len, 1);\n> +\telse if (skip_prefix_mem(cond, cond_len, \"worktree:\", &cond, &cond_len))\n> +\t\treturn include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,\n> +\t\t\t\t       cond, cond_len, 0);\n> +\telse if (skip_prefix_mem(cond, cond_len, \"worktree/i:\", &cond, &cond_len))\n> +\t\treturn include_by_path(kvi, inc->repo ? repo_get_work_tree(inc->repo) : NULL,\n> +\t\t\t\t       cond, cond_len, 1);\n>   \telse if (skip_prefix_mem(cond, cond_len, \"onbranch:\", &cond, &cond_len))\n>   \t\treturn include_by_branch(inc, cond, cond_len);\n>   \telse if (skip_prefix_mem(cond, cond_len, \"hasconfig:remote.*.url:\", &cond,\n> diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh\n> index 6e51f892f320..07b6fb649cd2 100755\n> --- a/t/t1305-config-include.sh\n> +++ b/t/t1305-config-include.sh\n> @@ -396,4 +396,117 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di\n>   \ttest_must_fail nongit git --git-dir=nonexistent config get foo.bar\n>   '\n>   \n> +# worktree: conditional include tests\n> +\n> +test_expect_success 'conditional include, worktree bare repo' '\n> +\tgit init --bare wt-bare &&\n> +\t(\n> +\t\tcd wt-bare &&\n> +\t\techo \"[includeIf \\\"worktree:/\\\"]path=bar-bare\" >>config &&\n> +\t\techo \"[test]wtbare=1\" >bar-bare &&\n> +\t\ttest_must_fail git config test.wtbare\n> +\t)\n> +'\n> +\n> +test_expect_success 'conditional include, worktree multiple worktrees' '\n> +\tgit init wt-multi &&\n> +\t(\n> +\t\tcd wt-multi &&\n> +\t\ttest_commit initial &&\n> +\t\tgit worktree add -b linked-branch ../wt-linked HEAD &&\n> +\t\tgit worktree add -b prefix-branch ../wt-prefix/linked HEAD\n> +\t) &&\n> +\twt_main=\"$(cd wt-multi && pwd)\" &&\n> +\twt_linked=\"$(cd wt-linked && pwd)\" &&\n> +\twt_prefix_parent=\"$(cd wt-prefix && pwd)\" &&\n> +\tcat >>wt-multi/.git/config <<-EOF &&\n> +\t[includeIf \"worktree:$wt_main\"]\n> +\t\tpath = main-config\n> +\t[includeIf \"worktree:$wt_linked\"]\n> +\t\tpath = linked-config\n> +\t[includeIf \"worktree:$wt_prefix_parent/\"]\n> +\t\tpath = prefix-config\n> +\tEOF\n> +\techo \"[test]mainvar=main\" >wt-multi/.git/main-config &&\n> +\techo \"[test]linkedvar=linked\" >wt-multi/.git/linked-config &&\n> +\techo \"[test]prefixvar=prefix\" >wt-multi/.git/prefix-config &&\n> +\techo main >expect &&\n> +\tgit -C wt-multi config test.mainvar >actual &&\n> +\ttest_cmp expect actual &&\n> +\ttest_must_fail git -C wt-multi config test.linkedvar &&\n> +\ttest_must_fail git -C wt-multi config test.prefixvar &&\n> +\techo linked >expect &&\n> +\tgit -C wt-linked config test.linkedvar >actual &&\n> +\ttest_cmp expect actual &&\n> +\ttest_must_fail git -C wt-linked config test.mainvar &&\n> +\ttest_must_fail git -C wt-linked config test.prefixvar &&\n> +\techo prefix >expect &&\n> +\tgit -C wt-prefix/linked config test.prefixvar >actual &&\n> +\ttest_cmp expect actual &&\n> +\ttest_must_fail git -C wt-prefix/linked config test.mainvar &&\n> +\ttest_must_fail git -C wt-prefix/linked config test.linkedvar\n> +'\n> +\n> +test_expect_success SYMLINKS 'conditional include, worktree resolves symlinks' '\n> +\tmkdir real-wt &&\n> +\tln -s real-wt link-wt &&\n> +\tgit init link-wt/repo &&\n> +\t(\n> +\t\tcd link-wt/repo &&\n> +\t\t# repo->worktree resolves symlinks, so use real path in pattern\n> +\t\techo \"[includeIf \\\"worktree:**/real-wt/repo\\\"]path=bar-link\" >>.git/config &&\n> +\t\techo \"[test]wtlink=2\" >.git/bar-link &&\n> +\t\techo 2 >expect &&\n> +\t\tgit config test.wtlink >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'conditional include, worktree, icase' '\n> +\tgit init wt-icase &&\n> +\t(\n> +\t\tcd wt-icase &&\n> +\t\ttest_commit initial &&\n> +\t\twt_path=\"$(pwd)\" &&\n> +\t\twt_upper=$(echo \"$wt_path\" | tr a-z A-Z) &&\n> +\t\techo \"[includeIf \\\"worktree/i:$wt_upper\\\"]path=icase-inc\" >>.git/config &&\n> +\t\techo \"[test]wticase=1\" >.git/icase-inc &&\n> +\t\techo 1 >expect &&\n> +\t\tgit config test.wticase >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n> +\n> +# The \"worktree\" condition cannot match during early config reading\n> +# because the repository object is not yet fully initialized and\n> +# repo_get_work_tree() returns NULL.\n> +test_expect_success 'conditional include, worktree does not match in early config' '\n> +\tgit init wt-early &&\n> +\t(\n> +\t\tcd wt-early &&\n> +\t\ttest_commit initial &&\n> +\t\twt_path=\"$(pwd)\" &&\n> +\t\techo \"[includeIf \\\"worktree:$wt_path\\\"]path=early-inc\" >>.git/config &&\n> +\t\techo \"[test]wtearly=1\" >.git/early-inc &&\n> +\t\ttest-tool config read_early_config test.wtearly >actual &&\n> +\t\ttest_must_be_empty actual\n> +\t)\n> +'\n> +\n> +test_expect_success 'conditional include, worktree without repository' '\n> +\ttest_when_finished \"rm -f .gitconfig config.inc\" &&\n> +\tgit config set -f .gitconfig \"includeIf.worktree:/.path\" config.inc &&\n> +\tgit config set -f config.inc foo.bar baz &&\n> +\tgit config get foo.bar &&\n> +\ttest_must_fail nongit git config get foo.bar\n> +'\n> +\n> +test_expect_success 'conditional include, worktree without repository but explicit nonexistent Git directory' '\n> +\ttest_when_finished \"rm -f .gitconfig config.inc\" &&\n> +\tgit config set -f .gitconfig \"includeIf.worktree:/.path\" config.inc &&\n> +\tgit config set -f config.inc foo.bar baz &&\n> +\tgit config get foo.bar &&\n> +\ttest_must_fail nongit git --git-dir=nonexistent config get foo.bar\n> +'\n> +\n>   test_done\n> \n\n"},{"id":"543823","messageId":"ag72CqZP7iFR8wWj@pks.im","threadId":"65629","inReplyTo":"20260513-includeif-worktree-v4-2-f8e6212d1fba@black-desk.cn","subject":"Re: [PATCH v4 2/2] config: add \"worktree\" and \"worktree/i\" includeIf conditions","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-21T12:09:46Z","receivedAt":"2026-05-21T12:09:54Z","isPatch":true,"body":"On Wed, May 13, 2026 at 04:08:18PM +0800, Chen Linxuan via B4 Relay wrote:\n> diff --git a/Documentation/config.adoc b/Documentation/config.adoc\n> index 62eebe7c5450..6299b1e3a019 100644\n> --- a/Documentation/config.adoc\n> +++ b/Documentation/config.adoc\n> @@ -146,6 +146,46 @@ refer to linkgit:gitignore[5] for details. For convenience:\n>  \tThis is the same as `gitdir` except that matching is done\n>  \tcase-insensitively (e.g. on case-insensitive file systems)\n>  \n> +`worktree`::\n> +\tThe data that follows the keyword `worktree` and a colon is used as a\n> +\tglob pattern. If the working directory of the current worktree matches\n> +\tthe pattern, the include condition is met.\n> ++\n> +The worktree location is the path where files are checked out (as returned\n> +by `git rev-parse --show-toplevel`). This is different from `gitdir`, which\n> +matches the `.git` directory path. In a linked worktree, the worktree path\n> +is the directory where that worktree's files are located, not the main\n> +repository's `.git` directory.\n\nNit: I feel like the first sentence already says it all, and the\nremainder is not adding much value. But I'm probably also quite biased\ngiven that I'm familiar with interals, so I don't insist on any change\nhere.\n\n[snip]\n> +While `extensions.worktreeConfig` (see linkgit:git-worktree[1]) also supports\n> +per-worktree configuration, it stores the config inside each repository's\n> +`.git/config.worktree` file and requires running `git config --worktree`\n> +inside each worktree individually. In contrast, `includeIf \"worktree:...\"`\n> +can be set once in a global or system-level configuration file (e.g.\n> +`~/.config/git/config`) and applies to all repositories at once based on\n> +their worktree location.\n\nNit: I tihnk saying \"global or system-level\" is totally sufficient,\nthere really is no need to explain where those files live.\n\nBut again, I'm not sure whether we need a new version for this change.\n\n> diff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh\n> index 6e51f892f320..07b6fb649cd2 100755\n> --- a/t/t1305-config-include.sh\n> +++ b/t/t1305-config-include.sh\n> @@ -396,4 +396,117 @@ test_expect_success 'onbranch without repository but explicit nonexistent Git di\n[snip]\n> +test_expect_success 'conditional include, worktree, icase' '\n> +\tgit init wt-icase &&\n> +\t(\n> +\t\tcd wt-icase &&\n> +\t\ttest_commit initial &&\n> +\t\twt_path=\"$(pwd)\" &&\n> +\t\twt_upper=$(echo \"$wt_path\" | tr a-z A-Z) &&\n> +\t\techo \"[includeIf \\\"worktree/i:$wt_upper\\\"]path=icase-inc\" >>.git/config &&\n> +\t\techo \"[test]wticase=1\" >.git/icase-inc &&\n> +\t\techo 1 >expect &&\n> +\t\tgit config test.wticase >actual &&\n> +\t\ttest_cmp expect actual\n> +\t)\n> +'\n\nAh, one more thing I didn't notice for the last version: it's good that\nwe have a check for the case-insensitive behaviour, but we're missing a\ntest that verifies that we're in fact case-sensitive by default. That\ntest would of course not work with a case-insensitive filesystem, but we\ncan depend on the `!CASE_INSENSITIVE_FS` prerequisite for that.\n\nOther than that this series looks good to me, thanks!\n\nPatrick\n"},{"id":"544022","messageId":"xmqqbje4grra.fsf@gitster.g","threadId":"65629","inReplyTo":"20260513-includeif-worktree-v4-0-f8e6212d1fba@black-desk.cn","subject":"Re: [PATCH v4 0/2] includeIf: add \"worktree\" condition for matching working tree path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-25T02:14:17Z","receivedAt":"2026-05-25T02:14:20Z","isPatch":true,"body":"Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>\nwrites:\n\n> The `includeIf` mechanism already supports matching on the `.git`\n> directory path (`gitdir`) and the currently checked out branch\n> (`onbranch`).  But in multi-worktree setups the `.git` directory of a\n> linked worktree points into the main repository's `.git/worktrees/`\n> area, which makes `gitdir` patterns cumbersome when one wants to\n> include config based on the working tree's checkout path instead.\n>\n> Introduce two new condition keywords:\n>\n>   - `worktree:<pattern>` matches the realpath of the current worktree's\n>     working directory against a glob pattern.\n>   - `worktree/i:<pattern>` is the case-insensitive variant.\n>\n> Supported pattern features: glob wildcards, `**/` and `/**`, `~`\n> expansion, `./` relative paths, and trailing-`/` prefix matching.\n> The condition never matches in a bare repository.\n>\n> Signed-off-by: Chen Linxuan <me@black-desk.cn>\n> ---\n\nThe test in this series fails in GitHub CI for Windows, it seems.\n\nhttps://github.com/git/git/actions/runs/26377220573/job/77639885088\n\n"},{"id":"544023","messageId":"CAC1kPDNKfm9Q=FWJkvpUSBmpmxL+RaOCifST8p=ViDwqVceNsg@mail.gmail.com","threadId":"65629","inReplyTo":"xmqqbje4grra.fsf@gitster.g","subject":"Re: [PATCH v4 0/2] includeIf: add \"worktree\" condition for matching working tree path","fromName":"Chen Linxuan","fromEmail":"me@black-desk.cn","sentAt":"2026-05-25T02:36:12Z","receivedAt":"2026-05-25T02:36:26Z","isPatch":true,"body":"On Mon, May 25, 2026 at 10:14 AM Junio C Hamano <gitster@pobox.com> wrote:\n>\n> Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>\n> writes:\n>\n> > The `includeIf` mechanism already supports matching on the `.git`\n> > directory path (`gitdir`) and the currently checked out branch\n> > (`onbranch`).  But in multi-worktree setups the `.git` directory of a\n> > linked worktree points into the main repository's `.git/worktrees/`\n> > area, which makes `gitdir` patterns cumbersome when one wants to\n> > include config based on the working tree's checkout path instead.\n> >\n> > Introduce two new condition keywords:\n> >\n> >   - `worktree:<pattern>` matches the realpath of the current worktree's\n> >     working directory against a glob pattern.\n> >   - `worktree/i:<pattern>` is the case-insensitive variant.\n> >\n> > Supported pattern features: glob wildcards, `**/` and `/**`, `~`\n> > expansion, `./` relative paths, and trailing-`/` prefix matching.\n> > The condition never matches in a bare repository.\n> >\n> > Signed-off-by: Chen Linxuan <me@black-desk.cn>\n> > ---\n>\n> The test in this series fails in GitHub CI for Windows, it seems.\n>\n> https://github.com/git/git/actions/runs/26377220573/job/77639885088\n\nIt seems that \"includeIf.worktree:/.path\" not working on windows.\n\nWill be updated in V5\n\n>\n>\n"},{"id":"544025","messageId":"xmqqwlwsfazz.fsf@gitster.g","threadId":"65629","inReplyTo":"CAC1kPDNKfm9Q=FWJkvpUSBmpmxL+RaOCifST8p=ViDwqVceNsg@mail.gmail.com","subject":"Re: [PATCH v4 0/2] includeIf: add \"worktree\" condition for matching working tree path","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-25T03:01:36Z","receivedAt":"2026-05-25T03:01:39Z","isPatch":true,"body":"Chen Linxuan <me@black-desk.cn> writes:\n\n> On Mon, May 25, 2026 at 10:14 AM Junio C Hamano <gitster@pobox.com> wrote:\n>>\n>> Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>\n>> writes:\n>>\n>> > The `includeIf` mechanism already supports matching on the `.git`\n>> > directory path (`gitdir`) and the currently checked out branch\n>> > (`onbranch`).  But in multi-worktree setups the `.git` directory of a\n>> > linked worktree points into the main repository's `.git/worktrees/`\n>> > area, which makes `gitdir` patterns cumbersome when one wants to\n>> > include config based on the working tree's checkout path instead.\n>> >\n>> > Introduce two new condition keywords:\n>> >\n>> >   - `worktree:<pattern>` matches the realpath of the current worktree's\n>> >     working directory against a glob pattern.\n>> >   - `worktree/i:<pattern>` is the case-insensitive variant.\n>> >\n>> > Supported pattern features: glob wildcards, `**/` and `/**`, `~`\n>> > expansion, `./` relative paths, and trailing-`/` prefix matching.\n>> > The condition never matches in a bare repository.\n>> >\n>> > Signed-off-by: Chen Linxuan <me@black-desk.cn>\n>> > ---\n>>\n>> The test in this series fails in GitHub CI for Windows, it seems.\n>>\n>> https://github.com/git/git/actions/runs/26377220573/job/77639885088\n>\n> It seems that \"includeIf.worktree:/.path\" not working on windows.\n>\n> Will be updated in V5\n\nThe topic has long been merged to 'next', but I've reverted the\ntopic out of 'next', so I can take a new iteration.\n\nThanks.\n"}]}