{"thread":{"id":"65550","subject":"[PATCH] dir: use per-worktree repository ignore patterns upon request","startedAt":"2026-04-24T17:09:39Z","lastAt":"2026-05-13T14:02:13Z","messageCount":10,"participants":["D. Ben Knoble","brian m. carlson","Phillip Wood","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"542266","messageId":"e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com","threadId":"65550","inReplyTo":null,"subject":"[PATCH] dir: use per-worktree repository ignore patterns upon request","fromName":"D. Ben Knoble","fromEmail":"ben.knoble+github@gmail.com","sentAt":"2026-04-24T17:09:19Z","receivedAt":"2026-04-24T17:09:39Z","isPatch":true,"body":"Today we have $GIT_DIR/info/exclude for the main worktree, but $(git\nrev-parse --git-dir)/info/exclude for secondary worktrees does not\nactually contribute to ignore specs; instead, secondary worktrees also\nuse $GIT_COMMON_DIR/info/exclude.\n\nSome users may prefer each worktree use its own ignore file; some may\nprefer both; some may prefer the current behavior.\n\nAdd, test, and document extensions.worktreeIgnore that controls which\nset of ignore files to use for worktrees.\n\nSigned-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n---\n\nNotes (benknoble/commits):\n    Discussed briefly at https://lore.kernel.org/git/CALnO6CCXmA+ATT7CuyWkU6P8qmLCCpMi5Ppr1c78s0heznpVyw@mail.gmail.com/T\n\n    This is based on next (4f69b47b94 (Merge branch 'ps/test-set-e-clean' into\n    next, 2026-04-23)) but cleanly applies to master (94f057755b (Git 2.54,\n    2026-04-19)) and seen (50541634cb (Merge branch\n    'js/parseopt-subcommand-autocorrection' into seen, 2026-04-23)).\n\n Documentation/config/extensions.adoc |  7 ++++\n Documentation/git-worktree.adoc      |  3 ++\n Documentation/gitignore.adoc         |  6 ++--\n dir.c                                | 48 +++++++++++++++++++++++---\n t/meson.build                        |  1 +\n t/t2408-worktree-ignore.sh           | 50 ++++++++++++++++++++++++++++\n 6 files changed, 109 insertions(+), 6 deletions(-)\n create mode 100755 t/t2408-worktree-ignore.sh\n\ndiff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc\nindex be6678bb5b..5bfb06a54b 100644\n--- a/Documentation/config/extensions.adoc\n+++ b/Documentation/config/extensions.adoc\n@@ -148,3 +148,10 @@ details.\n +\n For historical reasons, this extension is respected regardless of the\n `core.repositoryFormatVersion` setting.\n+\n+worktreeIgnore:::\n+\tIf enabled, then worktrees will load per-repository ignore files from\n+\t`$GIT_DIR/info/exclude` (that is,\n+\t`$GIT_COMMON_DIR/worktrees/<id>/info/exclude`). If set to `merge`, then\n+\tboth `$GIT_COMMON_DIR/info/exclude` and `$GIT_DIR/info/exclude` are\n+\tused.\ndiff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc\nindex fbf8426cd9..611c1e06b0 100644\n--- a/Documentation/git-worktree.adoc\n+++ b/Documentation/git-worktree.adoc\n@@ -412,6 +412,9 @@ linkgit:gitrepository-layout[5] for details.\n When `extensions.worktreeConfig` is enabled, the config file\n `.git/worktrees/<id>/config.worktree` is read after `.git/config` is.\n \n+See `extensions.worktreeIgnore` in linkgit:git-config[1] to control how\n+per-repository ignore files are found in worktrees.\n+\n LIST OUTPUT FORMAT\n ------------------\n The `worktree list` command has two output formats. The default format shows the\ndiff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc\nindex a3d24e5c34..d6976a44e4 100644\n--- a/Documentation/gitignore.adoc\n+++ b/Documentation/gitignore.adoc\n@@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore\n \n SYNOPSIS\n --------\n-$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\n+$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore\n \n DESCRIPTION\n -----------\n@@ -34,7 +34,9 @@ precedence, the last matching pattern decides the outcome):\n    includes such `.gitignore` files in its repository, containing patterns for\n    files generated as part of the project build.\n \n- * Patterns read from `$GIT_DIR/info/exclude`.\n+ * Patterns read from `$GIT_COMMON_DIR/info/exclude`. (See\n+   `extensions.worktreeIgnore` in linkgit:git-config[1] to change how this\n+   applies to worktrees.)\n \n  * Patterns read from the file specified by the configuration\n    variable `core.excludesFile`.\ndiff --git a/dir.c b/dir.c\nindex fcb8f6dd2a..9592eb0062 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -36,6 +36,7 @@\n #include \"trace2.h\"\n #include \"tree.h\"\n #include \"hex.h\"\n+#include \"worktree.h\"\n \n  /*\n   * The maximum size of a pattern/exclude file. If the file exceeds this size\n@@ -3478,6 +3479,23 @@ int remove_dir_recursively(struct strbuf *path, int flag)\n }\n \n static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n+static const char *git_worktree_info_exclude(void)\n+{\n+\tstatic const char *ret;\n+\tif (!ret)\n+\t{\n+\t\tconst struct worktree *wt = get_worktree_from_repository(the_repository);\n+\t\tret = worktree_git_path(wt, \"info/exclude\");\n+\t}\n+\treturn ret;\n+}\n+\n+static void standard_exclude_from_info(const char *path, struct dir_struct *dir)\n+{\n+\tstruct oid_stat *oid_stat = dir->untracked ? &dir->internal.ss_info_exclude : NULL;\n+\tif (!access_or_warn(path, R_OK, 0))\n+\t\tadd_patterns_from_file_1(dir, path, oid_stat);\n+}\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n@@ -3492,10 +3510,32 @@ void setup_standard_excludes(struct dir_struct *dir)\n \n \t/* per repository user preference */\n \tif (startup_info->have_repository) {\n-\t\tconst char *path = git_path_info_exclude();\n-\t\tif (!access_or_warn(path, R_OK, 0))\n-\t\t\tadd_patterns_from_file_1(dir, path,\n-\t\t\t\t\t\t dir->untracked ? &dir->internal.ss_info_exclude : NULL);\n+\t\t/* extensions.worktreeIgnore determines which includes we add */\n+\t\tconst char *wt_ignore;\n+\t\tint do_wt_ignore;\n+\t\tif (repo_config_get_value(the_repository,\n+\t\t\t\t\t  \"extensions.worktreeIgnore\",\n+\t\t\t\t\t  &wt_ignore)) {\n+\t\t\t/* unset: use main/.git/info/exclude */\n+\t\t\tstandard_exclude_from_info(git_path_info_exclude(), dir);\n+\t\t\treturn;\n+\t\t}\n+\t\tdo_wt_ignore = git_parse_maybe_bool(wt_ignore);\n+\t\tif (!do_wt_ignore) {\n+\t\t\t/* false: as above */\n+\t\t\tstandard_exclude_from_info(git_path_info_exclude(), dir);\n+\t\t} else if (do_wt_ignore > 0 || !strcmp(wt_ignore, \"worktree\")) {\n+\t\t\t/* true/worktree: use worktree/.git‡/info/exclude\n+\t\t\t * ‡: resolving .git */\n+\t\t\tstandard_exclude_from_info(git_worktree_info_exclude(), dir);\n+\t\t} else if (!strcmp(wt_ignore, \"merge\")) {\n+\t\t\t/* merge both! worktree last */\n+\t\t\tstandard_exclude_from_info(git_path_info_exclude(), dir);\n+\t\t\tstandard_exclude_from_info(git_worktree_info_exclude(), dir);\n+\t\t} else {\n+\t\t\tdie(_(\"invalid value for '%s': '%s'\"),\n+\t\t\t      \"extensions.worktreeIgnore\", wt_ignore);\n+\t\t}\n \t}\n }\n \ndiff --git a/t/meson.build b/t/meson.build\nindex 7528e5cda5..30624b59e8 100644\n--- a/t/meson.build\n+++ b/t/meson.build\n@@ -307,6 +307,7 @@ integration_tests = [\n   't2405-worktree-submodule.sh',\n   't2406-worktree-repair.sh',\n   't2407-worktree-heads.sh',\n+  't2408-worktree-ignore.sh',\n   't2500-untracked-overwriting.sh',\n   't2501-cwd-empty.sh',\n   't3000-ls-files-others.sh',\ndiff --git a/t/t2408-worktree-ignore.sh b/t/t2408-worktree-ignore.sh\nnew file mode 100755\nindex 0000000000..67644ddd8d\n--- /dev/null\n+++ b/t/t2408-worktree-ignore.sh\n@@ -0,0 +1,50 @@\n+#!/bin/sh\n+\n+test_description='extensions.worktreeIgnore'\n+\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\ttest_commit init &&\n+\tgit worktree add wt &&\n+\techo main >.git/info/exclude &&\n+\techo contents >main &&\n+\techo contents >wt/main &&\n+\twt_exclude=\"$(git -C wt rev-parse --git-dir)\"/info/exclude &&\n+\tmkdir -p \"$(dirname \"$wt_exclude\")\" &&\n+\techo worktree >\"$wt_exclude\" &&\n+\techo contents >worktree &&\n+\techo contents >wt/worktree\n+'\n+\n+test_ignore_main() {\n+\tgit check-ignore main &&\n+\tgit -C wt check-ignore main\n+}\n+\n+test_expect_success 'ignores main items by default' '\n+\ttest_ignore_main\n+'\n+\n+test_expect_success 'ignores main items with extensions.worktreeIgnore=no' '\n+\ttest_config extensions.worktreeIgnore no &&\n+\ttest_ignore_main\n+'\n+\n+test_expect_success 'ignores worktree items with extensions.worktreeIgnore=worktree' '\n+\ttest_config extensions.worktreeIgnore worktree &&\n+\tgit check-ignore main &&\n+\t! git check-ignore worktree &&\n+\t! git -C wt check-ignore main &&\n+\tgit -C wt check-ignore worktree\n+'\n+\n+test_expect_success 'ignores all items with extensions.worktreeIgnore=merge' '\n+\ttest_config extensions.worktreeIgnore merge &&\n+\tgit check-ignore main &&\n+\t! git check-ignore worktree &&\n+\tgit -C wt check-ignore main &&\n+\tgit -C wt check-ignore worktree\n+'\n+\n+test_done\n\nbase-commit: 4f69b47b940100b02630f745a52f9d9850f122b2\n-- \n2.54.0.rc2.544.gc7ae2d5bb8.dirty\n\n"},{"id":"542271","messageId":"aevGF3z5VtQLxJm3@fruit.crustytoothpaste.net","threadId":"65550","inReplyTo":"e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com","subject":"Re: [PATCH] dir: use per-worktree repository ignore patterns upon request","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2026-04-24T19:35:51Z","receivedAt":"2026-04-24T19:35:54Z","isPatch":true,"body":"On 2026-04-24 at 17:09:19, D. Ben Knoble wrote:\n> Today we have $GIT_DIR/info/exclude for the main worktree, but $(git\n> rev-parse --git-dir)/info/exclude for secondary worktrees does not\n> actually contribute to ignore specs; instead, secondary worktrees also\n> use $GIT_COMMON_DIR/info/exclude.\n> \n> Some users may prefer each worktree use its own ignore file; some may\n> prefer both; some may prefer the current behavior.\n> \n> Add, test, and document extensions.worktreeIgnore that controls which\n> set of ignore files to use for worktrees.\n\nI was wondering if we could make something like\n`extensions.worktreeInfoFiles` or something and also make the\n`info/attributes` (and maybe `info/sparse-checkout`) file used instead.\nI think that would be somewhat better than having additional options\nadded on after the fact and be a little more generally applicable.\n\nWhat do you think?  My apologies for not mentioning this sooner before\nyou'd already sent a patch.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"542272","messageId":"4a8d1289-2e2b-4fd6-8ada-143992dd7c4d@gmail.com","threadId":"65550","inReplyTo":"e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com","subject":"Re: [PATCH] dir: use per-worktree repository ignore patterns upon request","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-04-24T19:53:31Z","receivedAt":"2026-04-24T19:53:36Z","isPatch":true,"body":"Hi Ben\n\nOn 24/04/2026 18:09, D. Ben Knoble wrote:\n> Today we have $GIT_DIR/info/exclude for the main worktree, but $(git\n> rev-parse --git-dir)/info/exclude for secondary worktrees does not\n> actually contribute to ignore specs; instead, secondary worktrees also\n> use $GIT_COMMON_DIR/info/exclude.\n\nSharing the same set of exclude patterns between all worktrees seems \nlike a reasonable thing to do - it would be a pain to have to populate \nthem every time a new worktree was created.\n\n\tgit rev-parse --git-path info/exclude\n\nalways returns the correct path because it uses repo_git_path() which \nknows whether a particular path is per-worktree or resides under \n$GIT_COMMON_DIR. It is best to avoid constructing paths manually using \n\"git rev-parse --git-dir\"\n\n> Some users may prefer each worktree use its own ignore file; some may\n> prefer both; some may prefer the current behavior.\n\nThis sounds quite hypothetical - do we have a concrete use case for \nper-worktree exclude files?\n\nIf there is a use for per-worktree excludes it would be much better to \nuse a different path such as \"info.worktree/exclude\" so that the main \nworktree can have both shared and per-worktree excludes. We could use \n\"exclude.worktree\" like \"config.worktree\" but then we'd be in danger of \nadding new \".worktree\" files in the future if we want to add \nper-worktree versions of other files that live under \"info/\"\n\nThanks\n\nPhillip\n\n> Add, test, and document extensions.worktreeIgnore that controls which\n> set of ignore files to use for worktrees.\n> \n> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n> ---\n> \n> Notes (benknoble/commits):\n>      Discussed briefly at https://lore.kernel.org/git/CALnO6CCXmA+ATT7CuyWkU6P8qmLCCpMi5Ppr1c78s0heznpVyw@mail.gmail.com/T\n> \n>      This is based on next (4f69b47b94 (Merge branch 'ps/test-set-e-clean' into\n>      next, 2026-04-23)) but cleanly applies to master (94f057755b (Git 2.54,\n>      2026-04-19)) and seen (50541634cb (Merge branch\n>      'js/parseopt-subcommand-autocorrection' into seen, 2026-04-23)).\n> \n>   Documentation/config/extensions.adoc |  7 ++++\n>   Documentation/git-worktree.adoc      |  3 ++\n>   Documentation/gitignore.adoc         |  6 ++--\n>   dir.c                                | 48 +++++++++++++++++++++++---\n>   t/meson.build                        |  1 +\n>   t/t2408-worktree-ignore.sh           | 50 ++++++++++++++++++++++++++++\n>   6 files changed, 109 insertions(+), 6 deletions(-)\n>   create mode 100755 t/t2408-worktree-ignore.sh\n> \n> diff --git a/Documentation/config/extensions.adoc b/Documentation/config/extensions.adoc\n> index be6678bb5b..5bfb06a54b 100644\n> --- a/Documentation/config/extensions.adoc\n> +++ b/Documentation/config/extensions.adoc\n> @@ -148,3 +148,10 @@ details.\n>   +\n>   For historical reasons, this extension is respected regardless of the\n>   `core.repositoryFormatVersion` setting.\n> +\n> +worktreeIgnore:::\n> +\tIf enabled, then worktrees will load per-repository ignore files from\n> +\t`$GIT_DIR/info/exclude` (that is,\n> +\t`$GIT_COMMON_DIR/worktrees/<id>/info/exclude`). If set to `merge`, then\n> +\tboth `$GIT_COMMON_DIR/info/exclude` and `$GIT_DIR/info/exclude` are\n> +\tused.\n> diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc\n> index fbf8426cd9..611c1e06b0 100644\n> --- a/Documentation/git-worktree.adoc\n> +++ b/Documentation/git-worktree.adoc\n> @@ -412,6 +412,9 @@ linkgit:gitrepository-layout[5] for details.\n>   When `extensions.worktreeConfig` is enabled, the config file\n>   `.git/worktrees/<id>/config.worktree` is read after `.git/config` is.\n>   \n> +See `extensions.worktreeIgnore` in linkgit:git-config[1] to control how\n> +per-repository ignore files are found in worktrees.\n> +\n>   LIST OUTPUT FORMAT\n>   ------------------\n>   The `worktree list` command has two output formats. The default format shows the\n> diff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc\n> index a3d24e5c34..d6976a44e4 100644\n> --- a/Documentation/gitignore.adoc\n> +++ b/Documentation/gitignore.adoc\n> @@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore\n>   \n>   SYNOPSIS\n>   --------\n> -$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\n> +$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore\n>   \n>   DESCRIPTION\n>   -----------\n> @@ -34,7 +34,9 @@ precedence, the last matching pattern decides the outcome):\n>      includes such `.gitignore` files in its repository, containing patterns for\n>      files generated as part of the project build.\n>   \n> - * Patterns read from `$GIT_DIR/info/exclude`.\n> + * Patterns read from `$GIT_COMMON_DIR/info/exclude`. (See\n> +   `extensions.worktreeIgnore` in linkgit:git-config[1] to change how this\n> +   applies to worktrees.)\n>   \n>    * Patterns read from the file specified by the configuration\n>      variable `core.excludesFile`.\n> diff --git a/dir.c b/dir.c\n> index fcb8f6dd2a..9592eb0062 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -36,6 +36,7 @@\n>   #include \"trace2.h\"\n>   #include \"tree.h\"\n>   #include \"hex.h\"\n> +#include \"worktree.h\"\n>   \n>    /*\n>     * The maximum size of a pattern/exclude file. If the file exceeds this size\n> @@ -3478,6 +3479,23 @@ int remove_dir_recursively(struct strbuf *path, int flag)\n>   }\n>   \n>   static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n> +static const char *git_worktree_info_exclude(void)\n> +{\n> +\tstatic const char *ret;\n> +\tif (!ret)\n> +\t{\n> +\t\tconst struct worktree *wt = get_worktree_from_repository(the_repository);\n> +\t\tret = worktree_git_path(wt, \"info/exclude\");\n> +\t}\n> +\treturn ret;\n> +}\n> +\n> +static void standard_exclude_from_info(const char *path, struct dir_struct *dir)\n> +{\n> +\tstruct oid_stat *oid_stat = dir->untracked ? &dir->internal.ss_info_exclude : NULL;\n> +\tif (!access_or_warn(path, R_OK, 0))\n> +\t\tadd_patterns_from_file_1(dir, path, oid_stat);\n> +}\n>   \n>   void setup_standard_excludes(struct dir_struct *dir)\n>   {\n> @@ -3492,10 +3510,32 @@ void setup_standard_excludes(struct dir_struct *dir)\n>   \n>   \t/* per repository user preference */\n>   \tif (startup_info->have_repository) {\n> -\t\tconst char *path = git_path_info_exclude();\n> -\t\tif (!access_or_warn(path, R_OK, 0))\n> -\t\t\tadd_patterns_from_file_1(dir, path,\n> -\t\t\t\t\t\t dir->untracked ? &dir->internal.ss_info_exclude : NULL);\n> +\t\t/* extensions.worktreeIgnore determines which includes we add */\n> +\t\tconst char *wt_ignore;\n> +\t\tint do_wt_ignore;\n> +\t\tif (repo_config_get_value(the_repository,\n> +\t\t\t\t\t  \"extensions.worktreeIgnore\",\n> +\t\t\t\t\t  &wt_ignore)) {\n> +\t\t\t/* unset: use main/.git/info/exclude */\n> +\t\t\tstandard_exclude_from_info(git_path_info_exclude(), dir);\n> +\t\t\treturn;\n> +\t\t}\n> +\t\tdo_wt_ignore = git_parse_maybe_bool(wt_ignore);\n> +\t\tif (!do_wt_ignore) {\n> +\t\t\t/* false: as above */\n> +\t\t\tstandard_exclude_from_info(git_path_info_exclude(), dir);\n> +\t\t} else if (do_wt_ignore > 0 || !strcmp(wt_ignore, \"worktree\")) {\n> +\t\t\t/* true/worktree: use worktree/.git‡/info/exclude\n> +\t\t\t * ‡: resolving .git */\n> +\t\t\tstandard_exclude_from_info(git_worktree_info_exclude(), dir);\n> +\t\t} else if (!strcmp(wt_ignore, \"merge\")) {\n> +\t\t\t/* merge both! worktree last */\n> +\t\t\tstandard_exclude_from_info(git_path_info_exclude(), dir);\n> +\t\t\tstandard_exclude_from_info(git_worktree_info_exclude(), dir);\n> +\t\t} else {\n> +\t\t\tdie(_(\"invalid value for '%s': '%s'\"),\n> +\t\t\t      \"extensions.worktreeIgnore\", wt_ignore);\n> +\t\t}\n>   \t}\n>   }\n>   \n> diff --git a/t/meson.build b/t/meson.build\n> index 7528e5cda5..30624b59e8 100644\n> --- a/t/meson.build\n> +++ b/t/meson.build\n> @@ -307,6 +307,7 @@ integration_tests = [\n>     't2405-worktree-submodule.sh',\n>     't2406-worktree-repair.sh',\n>     't2407-worktree-heads.sh',\n> +  't2408-worktree-ignore.sh',\n>     't2500-untracked-overwriting.sh',\n>     't2501-cwd-empty.sh',\n>     't3000-ls-files-others.sh',\n> diff --git a/t/t2408-worktree-ignore.sh b/t/t2408-worktree-ignore.sh\n> new file mode 100755\n> index 0000000000..67644ddd8d\n> --- /dev/null\n> +++ b/t/t2408-worktree-ignore.sh\n> @@ -0,0 +1,50 @@\n> +#!/bin/sh\n> +\n> +test_description='extensions.worktreeIgnore'\n> +\n> +. ./test-lib.sh\n> +\n> +test_expect_success 'setup' '\n> +\ttest_commit init &&\n> +\tgit worktree add wt &&\n> +\techo main >.git/info/exclude &&\n> +\techo contents >main &&\n> +\techo contents >wt/main &&\n> +\twt_exclude=\"$(git -C wt rev-parse --git-dir)\"/info/exclude &&\n> +\tmkdir -p \"$(dirname \"$wt_exclude\")\" &&\n> +\techo worktree >\"$wt_exclude\" &&\n> +\techo contents >worktree &&\n> +\techo contents >wt/worktree\n> +'\n> +\n> +test_ignore_main() {\n> +\tgit check-ignore main &&\n> +\tgit -C wt check-ignore main\n> +}\n> +\n> +test_expect_success 'ignores main items by default' '\n> +\ttest_ignore_main\n> +'\n> +\n> +test_expect_success 'ignores main items with extensions.worktreeIgnore=no' '\n> +\ttest_config extensions.worktreeIgnore no &&\n> +\ttest_ignore_main\n> +'\n> +\n> +test_expect_success 'ignores worktree items with extensions.worktreeIgnore=worktree' '\n> +\ttest_config extensions.worktreeIgnore worktree &&\n> +\tgit check-ignore main &&\n> +\t! git check-ignore worktree &&\n> +\t! git -C wt check-ignore main &&\n> +\tgit -C wt check-ignore worktree\n> +'\n> +\n> +test_expect_success 'ignores all items with extensions.worktreeIgnore=merge' '\n> +\ttest_config extensions.worktreeIgnore merge &&\n> +\tgit check-ignore main &&\n> +\t! git check-ignore worktree &&\n> +\tgit -C wt check-ignore main &&\n> +\tgit -C wt check-ignore worktree\n> +'\n> +\n> +test_done\n> \n> base-commit: 4f69b47b940100b02630f745a52f9d9850f122b2\n\n"},{"id":"542285","messageId":"xmqq7bpvwxfp.fsf@gitster.g","threadId":"65550","inReplyTo":"4a8d1289-2e2b-4fd6-8ada-143992dd7c4d@gmail.com","subject":"Re: [PATCH] dir: use per-worktree repository ignore patterns upon request","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-04-25T03:06:34Z","receivedAt":"2026-04-25T03:06:37Z","isPatch":true,"body":"Phillip Wood <phillip.wood123@gmail.com> writes:\n\n> Sharing the same set of exclude patterns between all worktrees seems \n> like a reasonable thing to do - it would be a pain to have to populate \n> them every time a new worktree was created.\n>\n> \tgit rev-parse --git-path info/exclude\n>\n> always returns the correct path because it uses repo_git_path() which \n> knows whether a particular path is per-worktree or resides under \n> $GIT_COMMON_DIR. It is best to avoid constructing paths manually using \n> \"git rev-parse --git-dir\"\n\nGreat to see the best practice raised, like this.\n\n>> Some users may prefer each worktree use its own ignore file; some may\n>> prefer both; some may prefer the current behavior.\n>\n> This sounds quite hypothetical - do we have a concrete use case for \n> per-worktree exclude files?\n\nI can see if somebody wants to have different subset of paths\nchecked out by setting up separate sparse settings.  But marking\nwhat paths are never be part of the project (which is what exclude\nis about)?  I do not think it is useful in general.\n\nThe only case I can think of is a repository that houses more than\none completely unrelated histories, perhaps being cheapster on a\nhosting site that charges per number of repositories or something\nsilly like that.\n\nMy https://git.kernel.org/pub/scm/git/git.git/ repository houses two\ncompletely unrelated histories, one the project data, and 'todo'\nthat are mostly about tools I personally use to maintain the\nproject.  Ideally, the latter branch should have been created and\nmaintained as a separate repository, but back them I didn't have\nwrite access outside pub/scm/git/git.git/ (notably pub/scm/git/\nitself was not writable to me) and that was the only reason why that\nunrelated history is pushed into the same repository as a separate\nand unrelated 'todo' branch.  It is not a recommended practice, and\nif this configuration is primarily to cater to such layout, I do not\nthink we want to add it.\n"},{"id":"542923","messageId":"d58b6e921d3005c6170fc6c47f175214acb3fa68.1778249267.git.ben.knoble+github@gmail.com","threadId":"65550","inReplyTo":"e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com","subject":"[PATCH v2] ignore: note info/exclude lives in GIT_COMMON_DIR, not GIT_DIR","fromName":"D. Ben Knoble","fromEmail":"ben.knoble+github@gmail.com","sentAt":"2026-05-08T14:14:14Z","receivedAt":"2026-05-08T14:15:49Z","isPatch":true,"body":"gitignore(5) says that the per-repository ignore file is\n$GIT_DIR/info/exclude, but in a worktree that is not the case:\n\n    git rev-parse --git-path info/exclude\n    /path/to/main/worktree/.git/info/exclude\n    git rev-parse --git-common-dir\n    /path/to/main/worktree/.git\n\nWe actually use $GIT_COMMON_DIR/info/exclude. Adjust the documentation\nto say so.\n\nSigned-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n---\n\nNotes (benknoble/commits):\n    Changes in v2:\n    \n    Only adjust the documentation.\n    \n    brian points out that a more general extension would allow using more\n    info/ files as \"per-worktree,\" which I don't have the impetus to\n    implement myself.\n    \n    Phillip and Junio asked for a concrete use case:\n    \n        A colleague is developing a tool for managing the \"skill files\" of\n        various LLM tools (Claude, Windsurf, etc.). The files have\n        requirements that make it hard to generically ignore them (e.g.,\n        filenames and front-matter have to match), but different tasks\n        (corresponding to worktrees) may want different active skills, so it\n        is desirable to ignore the files. Think of this like node_modules.\n    \n        Unfortunately, since per-worktree ignores don't work, the current\n        solution is to put a .gitignore file in the corresponding directory\n        with the installed skills that ignores itself and the installed\n        skills.\n    \n    Since overall reactions seem fairly negative (or require a more general\n    extension, which I think is probably the right course but not simply\n    implemented), I've opted to adjust the docs. They originally confused\n    me, as I was surprised when my colleague reported that per-worktree\n    ignores didn't work (the docs imply they should by use of $GIT_DIR).\n    \n    Link to v1: <e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com>\n    \n    v1 notes:\n    \n    Discussed briefly at https://lore.kernel.org/git/CALnO6CCXmA+ATT7CuyWkU6P8qmLCCpMi5Ppr1c78s0heznpVyw@mail.gmail.com/T\n    \n    This is based on next (4f69b47b94 (Merge branch 'ps/test-set-e-clean'\n    into next, 2026-04-23)) but cleanly applies to master (94f057755b (Git\n    2.54, 2026-04-19)) and seen (50541634cb (Merge branch\n    'js/parseopt-subcommand-autocorrection' into seen, 2026-04-23)).\n\n Documentation/gitignore.adoc | 4 ++--\n 1 file changed, 2 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc\nindex a3d24e5c34..c423b650de 100644\n--- a/Documentation/gitignore.adoc\n+++ b/Documentation/gitignore.adoc\n@@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore\n \n SYNOPSIS\n --------\n-$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\n+$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore\n \n DESCRIPTION\n -----------\n@@ -34,7 +34,7 @@ precedence, the last matching pattern decides the outcome):\n    includes such `.gitignore` files in its repository, containing patterns for\n    files generated as part of the project build.\n \n- * Patterns read from `$GIT_DIR/info/exclude`.\n+ * Patterns read from `$GIT_COMMON_DIR/info/exclude`.\n \n  * Patterns read from the file specified by the configuration\n    variable `core.excludesFile`.\n\nbase-commit: 4f69b47b940100b02630f745a52f9d9850f122b2\n-- \n2.54.0.564.ge3ee0a11b5.dirty\n\n"},{"id":"542946","messageId":"af8_4cHEESb-Zit2@fruit.crustytoothpaste.net","threadId":"65550","inReplyTo":"d58b6e921d3005c6170fc6c47f175214acb3fa68.1778249267.git.ben.knoble+github@gmail.com","subject":"Re: [PATCH v2] ignore: note info/exclude lives in GIT_COMMON_DIR, not GIT_DIR","fromName":"brian m. carlson","fromEmail":"sandals@crustytoothpaste.net","sentAt":"2026-05-09T14:08:33Z","receivedAt":"2026-05-09T14:08:40Z","isPatch":true,"body":"On 2026-05-08 at 14:14:14, D. Ben Knoble wrote:\n> gitignore(5) says that the per-repository ignore file is\n> $GIT_DIR/info/exclude, but in a worktree that is not the case:\n> \n>     git rev-parse --git-path info/exclude\n>     /path/to/main/worktree/.git/info/exclude\n>     git rev-parse --git-common-dir\n>     /path/to/main/worktree/.git\n> \n> We actually use $GIT_COMMON_DIR/info/exclude. Adjust the documentation\n> to say so.\n\nThis seems quite reasonable.  I always appreciate documentation patches\nthat clarify things or fix inaccuracies.\n-- \nbrian m. carlson (they/them)\nToronto, Ontario, CA\n"},{"id":"543043","messageId":"fd05eba1-d821-4cbe-ada6-ccab97308e56@gmail.com","threadId":"65550","inReplyTo":"d58b6e921d3005c6170fc6c47f175214acb3fa68.1778249267.git.ben.knoble+github@gmail.com","subject":"Re: [PATCH v2] ignore: note info/exclude lives in GIT_COMMON_DIR, not GIT_DIR","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-11T10:30:19Z","receivedAt":"2026-05-11T10:30:19Z","isPatch":true,"body":"On 08/05/2026 15:14, D. Ben Knoble wrote:\n> gitignore(5) says that the per-repository ignore file is\n> $GIT_DIR/info/exclude, but in a worktree that is not the case:\n> \n>      git rev-parse --git-path info/exclude\n>      /path/to/main/worktree/.git/info/exclude\n>      git rev-parse --git-common-dir\n>      /path/to/main/worktree/.git\n> \n> We actually use $GIT_COMMON_DIR/info/exclude. Adjust the documentation\n> to say so.\n\nThanks for making the documentation match reality. Are there some more\ninstances than need to be changed? If I run\n\n     git grep -n GIT_DIR/info origin/master Documentation/gitignore.adoc\n\nI see\n\norigin/master:Documentation/gitignore.adoc:10:$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\norigin/master:Documentation/gitignore.adoc:37: * Patterns read from `$GIT_DIR/info/exclude`.\norigin/master:Documentation/gitignore.adoc:53:   the `$GIT_DIR/info/exclude` file.\norigin/master:Documentation/gitignore.adoc:100:   such as $GIT_DIR/info/exclude and core.excludesFile, are treated as if\norigin/master:Documentation/gitignore.adoc:149:`$GIT_DIR/info/exclude`. Patterns in the exclude file are used in addition to\norigin/master:Documentation/gitignore.adoc:150:those in `$GIT_DIR/info/exclude`.\n\nWe also have a ton of other files under Documentation that mention\n$GIT_DIR/info/... all of which, apart from the release notes, I\nthink should probably be using $GIT_COMMON_DIR but we can always do\nthat separately\n\nThanks\n\nPhillip\n\n> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n> ---\n> \n> Notes (benknoble/commits):\n>      Changes in v2:\n>      \n>      Only adjust the documentation.\n>      \n>      brian points out that a more general extension would allow using more\n>      info/ files as \"per-worktree,\" which I don't have the impetus to\n>      implement myself.\n>      \n>      Phillip and Junio asked for a concrete use case:\n>      \n>          A colleague is developing a tool for managing the \"skill files\" of\n>          various LLM tools (Claude, Windsurf, etc.). The files have\n>          requirements that make it hard to generically ignore them (e.g.,\n>          filenames and front-matter have to match), but different tasks\n>          (corresponding to worktrees) may want different active skills, so it\n>          is desirable to ignore the files. Think of this like node_modules.\n>      \n>          Unfortunately, since per-worktree ignores don't work, the current\n>          solution is to put a .gitignore file in the corresponding directory\n>          with the installed skills that ignores itself and the installed\n>          skills.\n>      \n>      Since overall reactions seem fairly negative (or require a more general\n>      extension, which I think is probably the right course but not simply\n>      implemented), I've opted to adjust the docs. They originally confused\n>      me, as I was surprised when my colleague reported that per-worktree\n>      ignores didn't work (the docs imply they should by use of $GIT_DIR).\n>      \n>      Link to v1: <e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com>\n>      \n>      v1 notes:\n>      \n>      Discussed briefly at https://lore.kernel.org/git/CALnO6CCXmA+ATT7CuyWkU6P8qmLCCpMi5Ppr1c78s0heznpVyw@mail.gmail.com/T\n>      \n>      This is based on next (4f69b47b94 (Merge branch 'ps/test-set-e-clean'\n>      into next, 2026-04-23)) but cleanly applies to master (94f057755b (Git\n>      2.54, 2026-04-19)) and seen (50541634cb (Merge branch\n>      'js/parseopt-subcommand-autocorrection' into seen, 2026-04-23)).\n> \n>   Documentation/gitignore.adoc | 4 ++--\n>   1 file changed, 2 insertions(+), 2 deletions(-)\n> \n> diff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc\n> index a3d24e5c34..c423b650de 100644\n> --- a/Documentation/gitignore.adoc\n> +++ b/Documentation/gitignore.adoc\n> @@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore\n>   \n>   SYNOPSIS\n>   --------\n> -$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\n> +$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore\n>   \n>   DESCRIPTION\n>   -----------\n> @@ -34,7 +34,7 @@ precedence, the last matching pattern decides the outcome):\n>      includes such `.gitignore` files in its repository, containing patterns for\n>      files generated as part of the project build.\n>   \n> - * Patterns read from `$GIT_DIR/info/exclude`.\n> + * Patterns read from `$GIT_COMMON_DIR/info/exclude`.\n>   \n>    * Patterns read from the file specified by the configuration\n>      variable `core.excludesFile`.\n> \n> base-commit: 4f69b47b940100b02630f745a52f9d9850f122b2\n\n"},{"id":"543087","messageId":"CALnO6CDeejkgXwDJwq2tmeUORf9DTP6kLEmsCoK4X_jcRMCEpw@mail.gmail.com","threadId":"65550","inReplyTo":"fd05eba1-d821-4cbe-ada6-ccab97308e56@gmail.com","subject":"Re: [PATCH v2] ignore: note info/exclude lives in GIT_COMMON_DIR, not GIT_DIR","fromName":"D. Ben Knoble","fromEmail":"ben.knoble+github@gmail.com","sentAt":"2026-05-11T19:55:00Z","receivedAt":"2026-05-11T19:55:13Z","isPatch":true,"body":"On Mon, May 11, 2026 at 6:30 AM Phillip Wood <phillip.wood123@gmail.com> wrote:\n>\n> On 08/05/2026 15:14, D. Ben Knoble wrote:\n> > gitignore(5) says that the per-repository ignore file is\n> > $GIT_DIR/info/exclude, but in a worktree that is not the case:\n> >\n> >      git rev-parse --git-path info/exclude\n> >      /path/to/main/worktree/.git/info/exclude\n> >      git rev-parse --git-common-dir\n> >      /path/to/main/worktree/.git\n> >\n> > We actually use $GIT_COMMON_DIR/info/exclude. Adjust the documentation\n> > to say so.\n>\n> Thanks for making the documentation match reality. Are there some more\n> instances than need to be changed? If I run\n>\n>      git grep -n GIT_DIR/info origin/master Documentation/gitignore.adoc\n>\n> I see\n>\n> origin/master:Documentation/gitignore.adoc:10:$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\n> origin/master:Documentation/gitignore.adoc:37: * Patterns read from `$GIT_DIR/info/exclude`.\n> origin/master:Documentation/gitignore.adoc:53:   the `$GIT_DIR/info/exclude` file.\n> origin/master:Documentation/gitignore.adoc:100:   such as $GIT_DIR/info/exclude and core.excludesFile, are treated as if\n> origin/master:Documentation/gitignore.adoc:149:`$GIT_DIR/info/exclude`. Patterns in the exclude file are used in addition to\n> origin/master:Documentation/gitignore.adoc:150:those in `$GIT_DIR/info/exclude`.\n>\n> We also have a ton of other files under Documentation that mention\n> $GIT_DIR/info/... all of which, apart from the release notes, I\n> think should probably be using $GIT_COMMON_DIR but we can always do\n> that separately\n>\n> Thanks\n>\n> Phillip\n\nGood catch, thanks. Let me adjust the ignore-related ones and leave\nthe rest for now.\n"},{"id":"543221","messageId":"ec97ad3f054e90b675f099a36a81a23bb4b2a0ed.1778620784.git.ben.knoble+github@gmail.com","threadId":"65550","inReplyTo":"d58b6e921d3005c6170fc6c47f175214acb3fa68.1778249267.git.ben.knoble+github@gmail.com","subject":"[PATCH v3] ignore: note info/exclude lives in GIT_COMMON_DIR, not GIT_DIR","fromName":"D. Ben Knoble","fromEmail":"ben.knoble+github@gmail.com","sentAt":"2026-05-12T21:21:43Z","receivedAt":"2026-05-12T21:22:22Z","isPatch":true,"body":"gitignore(5) says that the per-repository ignore file is\n$GIT_DIR/info/exclude, but in a worktree that is not the case:\n\n    git rev-parse --git-path info/exclude\n    /path/to/main/worktree/.git/info/exclude\n    git rev-parse --git-common-dir\n    /path/to/main/worktree/.git\n\nWe actually use $GIT_COMMON_DIR/info/exclude. Adjust the documentation\nand some code comments to say so.\n\nSigned-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n---\n\nNotes (benknoble/commits):\n    Changes in v3:\n    \n    Adjust more occurrences\n    \n    Link to v2: <d58b6e921d3005c6170fc6c47f175214acb3fa68.1778249267.git.ben.knoble+github@gmail.com>\n    \n    Changes in v2:\n    \n    Only adjust the documentation.\n    \n    brian points out that a more general extension would allow using more\n    info/ files as \"per-worktree,\" which I don't have the impetus to\n    implement myself.\n    \n    Phillip and Junio asked for a concrete use case:\n    \n        A colleague is developing a tool for managing the \"skill files\" of\n        various LLM tools (Claude, Windsurf, etc.). The files have\n        requirements that make it hard to generically ignore them (e.g.,\n        filenames and front-matter have to match), but different tasks\n        (corresponding to worktrees) may want different active skills, so it\n        is desirable to ignore the files. Think of this like node_modules.\n    \n        Unfortunately, since per-worktree ignores don't work, the current\n        solution is to put a .gitignore file in the corresponding directory\n        with the installed skills that ignores itself and the installed\n        skills.\n    \n    Since overall reactions seem fairly negative (or require a more general\n    extension, which I think is probably the right course but not simply\n    implemented), I've opted to adjust the docs. They originally confused\n    me, as I was surprised when my colleague reported that per-worktree\n    ignores didn't work (the docs imply they should by use of $GIT_DIR).\n    \n    Link to v1: <e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com>\n    \n    v1 notes:\n    \n    Discussed briefly at https://lore.kernel.org/git/CALnO6CCXmA+ATT7CuyWkU6P8qmLCCpMi5Ppr1c78s0heznpVyw@mail.gmail.com/T\n    \n    This is based on next (4f69b47b94 (Merge branch 'ps/test-set-e-clean'\n    into next, 2026-04-23)) but cleanly applies to master (94f057755b (Git\n    2.54, 2026-04-19)) and seen (50541634cb (Merge branch\n    'js/parseopt-subcommand-autocorrection' into seen, 2026-04-23)).\n\n Documentation/git-ls-files.adoc    |  2 +-\n Documentation/git-svn.adoc         |  2 +-\n Documentation/gitformat-index.adoc |  4 ++--\n Documentation/gitignore.adoc       | 12 ++++++------\n dir.c                              |  4 ++--\n dir.h                              |  2 +-\n 6 files changed, 13 insertions(+), 13 deletions(-)\n\ndiff --git a/Documentation/git-ls-files.adoc b/Documentation/git-ls-files.adoc\nindex 58c529afbe..2b175388e1 100644\n--- a/Documentation/git-ls-files.adoc\n+++ b/Documentation/git-ls-files.adoc\n@@ -331,7 +331,7 @@ can give `--exclude-per-directory=.gitignore`, and then specify:\n   1. The file specified by the `core.excludesfile` configuration\n      variable, if exists, or the `$XDG_CONFIG_HOME/git/ignore` file.\n \n-  2. The `$GIT_DIR/info/exclude` file.\n+  2. The `$GIT_COMMON_DIR/info/exclude` file.\n \n via the `--exclude-from=` option.\n \ndiff --git a/Documentation/git-svn.adoc b/Documentation/git-svn.adoc\nindex c26c12bab3..2a7fa60465 100644\n--- a/Documentation/git-svn.adoc\n+++ b/Documentation/git-svn.adoc\n@@ -439,7 +439,7 @@ Any other arguments are passed directly to 'git log'\n 'show-ignore'::\n \tRecursively finds and lists the svn:ignore and svn:global-ignores\n \tproperties on directories. The output is suitable for appending to\n-\tthe $GIT_DIR/info/exclude file.\n+\tthe $GIT_COMMON_DIR/info/exclude file.\n \n 'mkdirs'::\n \tAttempts to recreate empty directories that core Git cannot track\ndiff --git a/Documentation/gitformat-index.adoc b/Documentation/gitformat-index.adoc\nindex 145cace1fe..f6a427cb49 100644\n--- a/Documentation/gitformat-index.adoc\n+++ b/Documentation/gitformat-index.adoc\n@@ -291,14 +291,14 @@ Git index format\n     sequence in variable width encoding. Each string describes the\n     environment where the cache can be used.\n \n-  - Stat data of $GIT_DIR/info/exclude. See \"Index entry\" section from\n+  - Stat data of $GIT_COMMON_DIR/info/exclude. See \"Index entry\" section from\n     ctime field until \"file size\".\n \n   - Stat data of core.excludesFile\n \n   - 32-bit dir_flags (see struct dir_struct)\n \n-  - Hash of $GIT_DIR/info/exclude. A null hash means the file\n+  - Hash of $GIT_COMMON_DIR/info/exclude. A null hash means the file\n     does not exist.\n \n   - Hash of core.excludesFile. A null hash means the file does\ndiff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc\nindex a3d24e5c34..7979e50f18 100644\n--- a/Documentation/gitignore.adoc\n+++ b/Documentation/gitignore.adoc\n@@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore\n \n SYNOPSIS\n --------\n-$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\n+$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore\n \n DESCRIPTION\n -----------\n@@ -34,7 +34,7 @@ precedence, the last matching pattern decides the outcome):\n    includes such `.gitignore` files in its repository, containing patterns for\n    files generated as part of the project build.\n \n- * Patterns read from `$GIT_DIR/info/exclude`.\n+ * Patterns read from `$GIT_COMMON_DIR/info/exclude`.\n \n  * Patterns read from the file specified by the configuration\n    variable `core.excludesFile`.\n@@ -50,7 +50,7 @@ be used.\n    specific to a particular repository but which do not need to be shared\n    with other related repositories (e.g., auxiliary files that live inside\n    the repository but are specific to one user's workflow) should go into\n-   the `$GIT_DIR/info/exclude` file.\n+   the `$GIT_COMMON_DIR/info/exclude` file.\n \n  * Patterns which a user wants Git to\n    ignore in all situations (e.g., backup or temporary files generated by\n@@ -97,7 +97,7 @@ PATTERN FORMAT\n    match at any level below the `.gitignore` level.\n \n  - Patterns read from exclude sources that are outside the working tree,\n-   such as $GIT_DIR/info/exclude and core.excludesFile, are treated as if\n+   such as $GIT_COMMON_DIR/info/exclude and core.excludesFile, are treated as if\n    they are specified at the root of the working tree, i.e. a leading \"/\"\n    in such patterns anchors the match at the root of the repository.\n \n@@ -146,8 +146,8 @@ CONFIGURATION\n \n The optional configuration variable `core.excludesFile` indicates a path to a\n file containing patterns of file names to exclude, similar to\n-`$GIT_DIR/info/exclude`.  Patterns in the exclude file are used in addition to\n-those in `$GIT_DIR/info/exclude`.\n+`$GIT_COMMON_DIR/info/exclude`. Patterns in the exclude file are used in\n+addition to those in `$GIT_COMMON_DIR/info/exclude`.\n \n NOTES\n -----\ndiff --git a/dir.c b/dir.c\nindex fcb8f6dd2a..33c81c256e 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -2985,7 +2985,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \t\treturn NULL;\n \n \t/*\n-\t * We only support $GIT_DIR/info/exclude and core.excludesfile\n+\t * We only support $GIT_COMMON_DIR/info/exclude and core.excludesfile\n \t * as the global ignore rule files. Any other additions\n \t * (e.g. from command line) invalidate the cache. This\n \t * condition also catches running setup_standard_excludes()\n@@ -3078,7 +3078,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n \t\tistate->cache_changed |= UNTRACKED_CHANGED;\n \t}\n \n-\t/* Validate $GIT_DIR/info/exclude and core.excludesfile */\n+\t/* Validate $GIT_COMMON_DIR/info/exclude and core.excludesfile */\n \troot = dir->untracked->root;\n \tif (!oideq(&dir->internal.ss_info_exclude.oid,\n \t\t   &dir->untracked->ss_info_exclude.oid)) {\ndiff --git a/dir.h b/dir.h\nindex 20d4a078d6..83e0f648a8 100644\n--- a/dir.h\n+++ b/dir.h\n@@ -153,7 +153,7 @@ struct oid_stat {\n  *   - The list of files and directories of the directory in question\n  *   - The $GIT_DIR/index\n  *   - dir_struct flags\n- *   - The content of $GIT_DIR/info/exclude\n+ *   - The content of $GIT_COMMON_DIR/info/exclude\n  *   - The content of core.excludesfile\n  *   - The content (or the lack) of .gitignore of all parent directories\n  *     from $GIT_WORK_TREE\n\nbase-commit: 59709faab07346122d819453f4ad6f3ccdaf618e\n-- \n2.54.0.564.ge3ee0a11b5.dirty\n\n"},{"id":"543243","messageId":"bea48414-217b-4860-9279-fe94e3687c28@gmail.com","threadId":"65550","inReplyTo":"ec97ad3f054e90b675f099a36a81a23bb4b2a0ed.1778620784.git.ben.knoble+github@gmail.com","subject":"Re: [PATCH v3] ignore: note info/exclude lives in GIT_COMMON_DIR, not GIT_DIR","fromName":"Phillip Wood","fromEmail":"phillip.wood123@gmail.com","sentAt":"2026-05-13T14:02:06Z","receivedAt":"2026-05-13T14:02:13Z","isPatch":true,"body":"Hi Ben\n\nOn 12/05/2026 22:21, D. Ben Knoble wrote:\n> gitignore(5) says that the per-repository ignore file is\n> $GIT_DIR/info/exclude, but in a worktree that is not the case:\n> \n>      git rev-parse --git-path info/exclude\n>      /path/to/main/worktree/.git/info/exclude\n>      git rev-parse --git-common-dir\n>      /path/to/main/worktree/.git\n> \n> We actually use $GIT_COMMON_DIR/info/exclude. Adjust the documentation\n> and some code comments to say so.\n\nThanks for the re-roll, this looks good to me\n\nPhillip\n\n> Signed-off-by: D. Ben Knoble <ben.knoble+github@gmail.com>\n> ---\n> \n> Notes (benknoble/commits):\n>      Changes in v3:\n>      \n>      Adjust more occurrences\n>      \n>      Link to v2: <d58b6e921d3005c6170fc6c47f175214acb3fa68.1778249267.git.ben.knoble+github@gmail.com>\n>      \n>      Changes in v2:\n>      \n>      Only adjust the documentation.\n>      \n>      brian points out that a more general extension would allow using more\n>      info/ files as \"per-worktree,\" which I don't have the impetus to\n>      implement myself.\n>      \n>      Phillip and Junio asked for a concrete use case:\n>      \n>          A colleague is developing a tool for managing the \"skill files\" of\n>          various LLM tools (Claude, Windsurf, etc.). The files have\n>          requirements that make it hard to generically ignore them (e.g.,\n>          filenames and front-matter have to match), but different tasks\n>          (corresponding to worktrees) may want different active skills, so it\n>          is desirable to ignore the files. Think of this like node_modules.\n>      \n>          Unfortunately, since per-worktree ignores don't work, the current\n>          solution is to put a .gitignore file in the corresponding directory\n>          with the installed skills that ignores itself and the installed\n>          skills.\n>      \n>      Since overall reactions seem fairly negative (or require a more general\n>      extension, which I think is probably the right course but not simply\n>      implemented), I've opted to adjust the docs. They originally confused\n>      me, as I was surprised when my colleague reported that per-worktree\n>      ignores didn't work (the docs imply they should by use of $GIT_DIR).\n>      \n>      Link to v1: <e3ee0a11b566dd2cc605447c111ae4620bce0fe6.1777050300.git.ben.knoble+github@gmail.com>\n>      \n>      v1 notes:\n>      \n>      Discussed briefly at https://lore.kernel.org/git/CALnO6CCXmA+ATT7CuyWkU6P8qmLCCpMi5Ppr1c78s0heznpVyw@mail.gmail.com/T\n>      \n>      This is based on next (4f69b47b94 (Merge branch 'ps/test-set-e-clean'\n>      into next, 2026-04-23)) but cleanly applies to master (94f057755b (Git\n>      2.54, 2026-04-19)) and seen (50541634cb (Merge branch\n>      'js/parseopt-subcommand-autocorrection' into seen, 2026-04-23)).\n> \n>   Documentation/git-ls-files.adoc    |  2 +-\n>   Documentation/git-svn.adoc         |  2 +-\n>   Documentation/gitformat-index.adoc |  4 ++--\n>   Documentation/gitignore.adoc       | 12 ++++++------\n>   dir.c                              |  4 ++--\n>   dir.h                              |  2 +-\n>   6 files changed, 13 insertions(+), 13 deletions(-)\n> \n> diff --git a/Documentation/git-ls-files.adoc b/Documentation/git-ls-files.adoc\n> index 58c529afbe..2b175388e1 100644\n> --- a/Documentation/git-ls-files.adoc\n> +++ b/Documentation/git-ls-files.adoc\n> @@ -331,7 +331,7 @@ can give `--exclude-per-directory=.gitignore`, and then specify:\n>     1. The file specified by the `core.excludesfile` configuration\n>        variable, if exists, or the `$XDG_CONFIG_HOME/git/ignore` file.\n>   \n> -  2. The `$GIT_DIR/info/exclude` file.\n> +  2. The `$GIT_COMMON_DIR/info/exclude` file.\n>   \n>   via the `--exclude-from=` option.\n>   \n> diff --git a/Documentation/git-svn.adoc b/Documentation/git-svn.adoc\n> index c26c12bab3..2a7fa60465 100644\n> --- a/Documentation/git-svn.adoc\n> +++ b/Documentation/git-svn.adoc\n> @@ -439,7 +439,7 @@ Any other arguments are passed directly to 'git log'\n>   'show-ignore'::\n>   \tRecursively finds and lists the svn:ignore and svn:global-ignores\n>   \tproperties on directories. The output is suitable for appending to\n> -\tthe $GIT_DIR/info/exclude file.\n> +\tthe $GIT_COMMON_DIR/info/exclude file.\n>   \n>   'mkdirs'::\n>   \tAttempts to recreate empty directories that core Git cannot track\n> diff --git a/Documentation/gitformat-index.adoc b/Documentation/gitformat-index.adoc\n> index 145cace1fe..f6a427cb49 100644\n> --- a/Documentation/gitformat-index.adoc\n> +++ b/Documentation/gitformat-index.adoc\n> @@ -291,14 +291,14 @@ Git index format\n>       sequence in variable width encoding. Each string describes the\n>       environment where the cache can be used.\n>   \n> -  - Stat data of $GIT_DIR/info/exclude. See \"Index entry\" section from\n> +  - Stat data of $GIT_COMMON_DIR/info/exclude. See \"Index entry\" section from\n>       ctime field until \"file size\".\n>   \n>     - Stat data of core.excludesFile\n>   \n>     - 32-bit dir_flags (see struct dir_struct)\n>   \n> -  - Hash of $GIT_DIR/info/exclude. A null hash means the file\n> +  - Hash of $GIT_COMMON_DIR/info/exclude. A null hash means the file\n>       does not exist.\n>   \n>     - Hash of core.excludesFile. A null hash means the file does\n> diff --git a/Documentation/gitignore.adoc b/Documentation/gitignore.adoc\n> index a3d24e5c34..7979e50f18 100644\n> --- a/Documentation/gitignore.adoc\n> +++ b/Documentation/gitignore.adoc\n> @@ -7,7 +7,7 @@ gitignore - Specifies intentionally untracked files to ignore\n>   \n>   SYNOPSIS\n>   --------\n> -$XDG_CONFIG_HOME/git/ignore, $GIT_DIR/info/exclude, .gitignore\n> +$XDG_CONFIG_HOME/git/ignore, $GIT_COMMON_DIR/info/exclude, .gitignore\n>   \n>   DESCRIPTION\n>   -----------\n> @@ -34,7 +34,7 @@ precedence, the last matching pattern decides the outcome):\n>      includes such `.gitignore` files in its repository, containing patterns for\n>      files generated as part of the project build.\n>   \n> - * Patterns read from `$GIT_DIR/info/exclude`.\n> + * Patterns read from `$GIT_COMMON_DIR/info/exclude`.\n>   \n>    * Patterns read from the file specified by the configuration\n>      variable `core.excludesFile`.\n> @@ -50,7 +50,7 @@ be used.\n>      specific to a particular repository but which do not need to be shared\n>      with other related repositories (e.g., auxiliary files that live inside\n>      the repository but are specific to one user's workflow) should go into\n> -   the `$GIT_DIR/info/exclude` file.\n> +   the `$GIT_COMMON_DIR/info/exclude` file.\n>   \n>    * Patterns which a user wants Git to\n>      ignore in all situations (e.g., backup or temporary files generated by\n> @@ -97,7 +97,7 @@ PATTERN FORMAT\n>      match at any level below the `.gitignore` level.\n>   \n>    - Patterns read from exclude sources that are outside the working tree,\n> -   such as $GIT_DIR/info/exclude and core.excludesFile, are treated as if\n> +   such as $GIT_COMMON_DIR/info/exclude and core.excludesFile, are treated as if\n>      they are specified at the root of the working tree, i.e. a leading \"/\"\n>      in such patterns anchors the match at the root of the repository.\n>   \n> @@ -146,8 +146,8 @@ CONFIGURATION\n>   \n>   The optional configuration variable `core.excludesFile` indicates a path to a\n>   file containing patterns of file names to exclude, similar to\n> -`$GIT_DIR/info/exclude`.  Patterns in the exclude file are used in addition to\n> -those in `$GIT_DIR/info/exclude`.\n> +`$GIT_COMMON_DIR/info/exclude`. Patterns in the exclude file are used in\n> +addition to those in `$GIT_COMMON_DIR/info/exclude`.\n>   \n>   NOTES\n>   -----\n> diff --git a/dir.c b/dir.c\n> index fcb8f6dd2a..33c81c256e 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -2985,7 +2985,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n>   \t\treturn NULL;\n>   \n>   \t/*\n> -\t * We only support $GIT_DIR/info/exclude and core.excludesfile\n> +\t * We only support $GIT_COMMON_DIR/info/exclude and core.excludesfile\n>   \t * as the global ignore rule files. Any other additions\n>   \t * (e.g. from command line) invalidate the cache. This\n>   \t * condition also catches running setup_standard_excludes()\n> @@ -3078,7 +3078,7 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d\n>   \t\tistate->cache_changed |= UNTRACKED_CHANGED;\n>   \t}\n>   \n> -\t/* Validate $GIT_DIR/info/exclude and core.excludesfile */\n> +\t/* Validate $GIT_COMMON_DIR/info/exclude and core.excludesfile */\n>   \troot = dir->untracked->root;\n>   \tif (!oideq(&dir->internal.ss_info_exclude.oid,\n>   \t\t   &dir->untracked->ss_info_exclude.oid)) {\n> diff --git a/dir.h b/dir.h\n> index 20d4a078d6..83e0f648a8 100644\n> --- a/dir.h\n> +++ b/dir.h\n> @@ -153,7 +153,7 @@ struct oid_stat {\n>    *   - The list of files and directories of the directory in question\n>    *   - The $GIT_DIR/index\n>    *   - dir_struct flags\n> - *   - The content of $GIT_DIR/info/exclude\n> + *   - The content of $GIT_COMMON_DIR/info/exclude\n>    *   - The content of core.excludesfile\n>    *   - The content (or the lack) of .gitignore of all parent directories\n>    *     from $GIT_WORK_TREE\n> \n> base-commit: 59709faab07346122d819453f4ad6f3ccdaf618e\n\n"}]}