{"thread":{"id":"65412","subject":"[PATCH v2 3/3] t1305: add tests for includeIf \"worktree\"","startedAt":"2026-04-02T02:58:53Z","lastAt":"2026-04-02T21:22:24Z","messageCount":5,"participants":["Chen Linxuan via B4 Relay","Junio C Hamano"],"isPatch":true,"patchVersion":2,"patchTotal":3},"messages":[{"id":"540681","messageId":"20260402-includeif-worktree-v2-3-36e339b898d7@black-desk.cn","threadId":"65412","inReplyTo":"20260402-includeif-worktree-v2-0-36e339b898d7@black-desk.cn","subject":"[PATCH v2 3/3] t1305: add tests for includeIf \"worktree\"","fromName":"Chen Linxuan via B4 Relay","fromEmail":"devnull+me.black-desk.cn@kernel.org","sentAt":"2026-04-02T02:58:47Z","receivedAt":"2026-04-02T02:58:53Z","isPatch":true,"body":"From: Chen Linxuan <me@black-desk.cn>\n\nCover the following scenarios unique to the \"worktree\" condition\n(path matching features such as glob, tilde, icase are already\nexercised by the gitdir tests):\n\n  - bare repository (condition must not match)\n  - multiple worktrees: main and linked worktrees each match their\n    own path-based condition while sharing a single config file;\n    a third linked worktree verifies directory-prefix matching with\n    a trailing slash\n  - symlinked worktree: the path is resolved before matching\n\nSigned-off-by: Chen Linxuan <me@black-desk.cn>\n---\n t/t1305-config-include.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 66 insertions(+)\n\ndiff --git a/t/t1305-config-include.sh b/t/t1305-config-include.sh\nindex 6e51f892f320..8a5ba4b884d3 100755\n--- a/t/t1305-config-include.sh\n+++ b/t/t1305-config-include.sh\n@@ -396,4 +396,70 @@ 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_done\n\n-- \n2.53.0\n\n\n"},{"id":"540682","messageId":"20260402-includeif-worktree-v2-2-36e339b898d7@black-desk.cn","threadId":"65412","inReplyTo":"20260402-includeif-worktree-v2-0-36e339b898d7@black-desk.cn","subject":"[PATCH v2 2/3] Documentation/config: add includeIf \"worktree\"","fromName":"Chen Linxuan via B4 Relay","fromEmail":"devnull+me.black-desk.cn@kernel.org","sentAt":"2026-04-02T02:58:46Z","receivedAt":"2026-04-02T02:58:53Z","isPatch":true,"body":"From: Chen Linxuan <me@black-desk.cn>\n\nAdd documentation for the newly introduced `worktree` and `worktree/i`\nconditional include keywords.  Describe how they differ from `gitdir`\n(matching the working tree checkout path instead of the `.git`\ndirectory path), and list the supported pattern features: glob\nwildcards, `**/` and `/**`, `~` expansion, `./` relative paths, and\ntrailing-`/` prefix matching.  Note that the condition never matches\nin a bare repository.  Also add usage examples alongside the existing\n`gitdir` examples.\n\nSigned-off-by: Chen Linxuan <me@black-desk.cn>\n---\n Documentation/config.adoc | 50 +++++++++++++++++++++++++++++++++++++++++++++++\n 1 file changed, 50 insertions(+)\n\ndiff --git a/Documentation/config.adoc b/Documentation/config.adoc\nindex 62eebe7c5450..a4f3ec905098 100644\n--- a/Documentation/config.adoc\n+++ b/Documentation/config.adoc\n@@ -146,6 +146,48 @@ 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 can contain standard globbing wildcards and two additional\n+ones, `**/` and `/**`, that can match multiple path components. Please\n+refer to linkgit:gitignore[5] for details. For convenience:\n+\n+ * If the pattern starts with `~/`, `~` will be substituted with the\n+   content of the environment variable `HOME`.\n+\n+ * If the pattern starts with `./`, it is replaced with the directory\n+   containing the current config file.\n+\n+ * If the pattern does not start with either `~/`, `./` or `/`, `**/`\n+   will be automatically prepended. For example, the pattern `foo/bar`\n+   becomes `**/foo/bar` and would match `/any/path/to/foo/bar`.\n+\n+ * If the pattern ends with `/`, `**` will be automatically added. For\n+   example, the pattern `foo/` becomes `foo/**`. In other words, it\n+   matches \"foo\" and everything inside, recursively.\n++\n+This condition will never match in a bare repository (which has no worktree).\n++\n+This is useful when you need to use different `user.name`, `user.email`, or\n+GPG keys in different worktrees of the same repository. While\n+`extensions.worktreeConfig` also allows per-worktree configuration, it\n+requires changes inside each repository. This condition can be set in the\n+user's global configuration file (e.g. `~/.config/git/config`) and applies\n+to multiple repositories at once.\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 +286,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\n-- \n2.53.0\n\n\n"},{"id":"540683","messageId":"20260402-includeif-worktree-v2-0-36e339b898d7@black-desk.cn","threadId":"65412","inReplyTo":null,"subject":"[PATCH v2 0/3] 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-04-02T02:58:44Z","receivedAt":"2026-04-02T02:58:53Z","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 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 (3):\n      config: add \"worktree\" and \"worktree/i\" includeIf conditions\n      Documentation/config: add includeIf \"worktree\"\n      t1305: add tests for includeIf \"worktree\"\n\n Documentation/config.adoc | 50 +++++++++++++++++++++++++++++++++++\n config.c                  | 25 ++++++++++--------\n t/t1305-config-include.sh | 66 +++++++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 130 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":"540684","messageId":"20260402-includeif-worktree-v2-1-36e339b898d7@black-desk.cn","threadId":"65412","inReplyTo":"20260402-includeif-worktree-v2-0-36e339b898d7@black-desk.cn","subject":"[PATCH v2 1/3] config: add \"worktree\" and \"worktree/i\" includeIf conditions","fromName":"Chen Linxuan via B4 Relay","fromEmail":"devnull+me.black-desk.cn@kernel.org","sentAt":"2026-04-02T02:58:45Z","receivedAt":"2026-04-02T02:58:53Z","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 follows the same structure as `include_by_gitdir()`:\nthe worktree path is resolved via `strbuf_realpath()`, the condition\npattern is prepared with `prepare_include_condition_pattern()` (which\nhandles `~` expansion, `./` relative paths, `**/` prefix insertion and\ntrailing-`/` expansion), and matching is done with `wildmatch()`.  A\nsecond attempt using `strbuf_add_absolute_path()` is performed to\nhandle symlinked paths.\n\nThe condition never matches in bare repositories (where there is no\nworktree) or during early config reading (where no repository is\navailable).\n\nSigned-off-by: Chen Linxuan <me@black-desk.cn>\n---\n config.c | 25 ++++++++++++++-----------\n 1 file changed, 14 insertions(+), 11 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex 156f2a24fa00..6d0c2d0725e4 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,15 @@ 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, \"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\n-- \n2.53.0\n\n\n"},{"id":"540797","messageId":"xmqqv7e9njld.fsf@gitster.g","threadId":"65412","inReplyTo":"20260402-includeif-worktree-v2-1-36e339b898d7@black-desk.cn","subject":"Re: [PATCH v2 1/3] config: add \"worktree\" and \"worktree/i\" includeIf conditions","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-02T21:22:22Z","receivedAt":"2026-04-02T21:22:24Z","isPatch":true,"body":"Chen Linxuan via B4 Relay <devnull+me.black-desk.cn@kernel.org>\nwrites:\n\n> From: Chen Linxuan <me@black-desk.cn>\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 (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>\n> The implementation follows the same structure as `include_by_gitdir()`:\n> the worktree path is resolved via `strbuf_realpath()`, the condition\n> pattern is prepared with `prepare_include_condition_pattern()` (which\n> handles `~` expansion, `./` relative paths, `**/` prefix insertion and\n> trailing-`/` expansion), and matching is done with `wildmatch()`.  A\n> second attempt using `strbuf_add_absolute_path()` is performed to\n> handle symlinked paths.\n>\n> The condition never matches in bare repositories (where there is no\n> worktree) or during early config reading (where no repository is\n> available).\n>\n> Signed-off-by: Chen Linxuan <me@black-desk.cn>\n> ---\n>  config.c | 25 ++++++++++++++-----------\n>  1 file changed, 14 insertions(+), 11 deletions(-)\n\nThis probably should be further split into two commits, one that\nintroduces the \"by-path\" helper function and reimplements the\n\"gitdir\" and \"gitdir/i\" support with it (i.e., essentially it\namounts to dropping of \"struct config_options *\" and using \"const\nchar *path\" in place of it, I presume), and another patch that adds\nworktree support in terms of the \"by-path\" helper.  \n\nThe documentation updates and additional test should probably be\ndone as part of the second half of this patch, unless the second half\nof this patch to add \"worktree\" and \"worktree/i\" is too big to be\nreviewed standalone (which I do not think would be the case).\n\n> diff --git a/config.c b/config.c\n> index 156f2a24fa00..6d0c2d0725e4 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,15 @@ 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, \"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"}]}