threads / patch / 64647

patchenvironment: move "core.attributesFile" into repo-setting

Subject: [Outreachy PATCH] environment: move "core.attributesFile" into repo-setting

## tl;dr

21 messages between Dec 18, 2025 and Jan 7, 2026. Diffs are folded; open one to read it.

replies: 20people: 4as markdown or json

Olamide Caleb Bello· Dec 18, 2025, 08:30 UTC · lore

When handling multiple repositories within the same process, relying on global state for accessing the "core.attributesFile" configuration can lead to incorrect values being used. It also makes it harder to isolate repositories and hinders the libification of git. The functions `bootstrap_attr_stack()` and `git_attr_val_system()` retrieve "core.attributesFile" via `git_attr_global_file()` which reads from global state `git_attributes_file`.

Move the "core.attributesFile" configuration into the `struct repo_settings` instead of relying on the global state. A new function `repo_settings_get_attributesfile_path()` is added and used to retrieve this setting in a repository-scoped manner. The functions to retrieve "core.attributesFile" are replaced with the new accessor function `repo_settings_get_attributesfile_path()` This improves multi-repository behaviour and aligns with the goal of libifying of Git.

Note that in `bootstrap_attr_stack()`, the `index_state` is used only if it exists, else we default to `the_repository`.

Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
---
The link to the GitHub CI is provided below
https://github.com/cloobTech/git/actions/runs/20284228144
 attr.c          | 20 +++++++++-----------
 attr.h          |  3 ---
 builtin/var.c   |  2 +-
 environment.c   |  6 ------
 environment.h   |  1 -
 repo-settings.c | 10 ++++++++++
 repo-settings.h |  8 ++++++++
 7 files changed, 28 insertions(+), 22 deletions(-)
Show changes to 7 files +28 −22

attr.c, attr.h, builtin/var.c, environment.c, environment.h, repo-settings.c, repo-settings.h

diff --git a/attr.c b/attr.c
index 4999b7e09d..9e51f8e70b 100644
--- a/attr.c
+++ b/attr.c
@@ -879,14 +879,6 @@ const char *git_attr_system_file(void)
 	return system_wide;
 }
 
-const char *git_attr_global_file(void)
-{
-	if (!git_attributes_file)
-		git_attributes_file = xdg_config_home("attributes");
-
-	return git_attributes_file;
-}
-
 int git_attr_system_is_enabled(void)
 {
 	return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
@@ -912,6 +904,8 @@ static void bootstrap_attr_stack(struct index_state *istate,
 {
 	struct attr_stack *e;
 	unsigned flags = READ_ATTR_MACRO_OK;
+	const char *attributes_file_path;
+	struct repository *repo;
 
 	if (*stack)
 		return;
@@ -926,9 +920,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
 		push_stack(stack, e, NULL, 0);
 	}
 
-	/* home directory */
-	if (git_attr_global_file()) {
-		e = read_attr_from_file(git_attr_global_file(), flags);
+	if (istate && istate->repo)
+		repo = istate->repo;
+	else
+		repo = the_repository;
+	attributes_file_path = repo_settings_get_attributesfile_path(repo);
+	if (attributes_file_path) {
+		e = read_attr_from_file(attributes_file_path, flags);
 		push_stack(stack, e, NULL, 0);
 	}
 
diff --git a/attr.h b/attr.h
index a04a521092..956ce6ba62 100644
--- a/attr.h
+++ b/attr.h
@@ -232,9 +232,6 @@ void attr_start(void);
 /* Return the system gitattributes file. */
 const char *git_attr_system_file(void);
 
-/* Return the global gitattributes file, if any. */
-const char *git_attr_global_file(void);
-
 /* Return whether the system gitattributes file is enabled and should be used. */
 int git_attr_system_is_enabled(void);
 
diff --git a/builtin/var.c b/builtin/var.c
index cc3a43cde2..fd577f2930 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -72,7 +72,7 @@ static char *git_attr_val_system(int ident_flag UNUSED)
 
 static char *git_attr_val_global(int ident_flag UNUSED)
 {
-	char *file = xstrdup_or_null(git_attr_global_file());
+	char *file = xstrdup_or_null(repo_settings_get_attributesfile_path(the_repository));
 	if (file) {
 		normalize_path_copy(file, file);
 		return file;
diff --git a/environment.c b/environment.c
index a770b5921d..ed7d8f42d9 100644
--- a/environment.c
+++ b/environment.c
@@ -53,7 +53,6 @@ char *git_commit_encoding;
 char *git_log_output_encoding;
 char *apply_default_whitespace;
 char *apply_default_ignorewhitespace;
-char *git_attributes_file;
 int zlib_compression_level = Z_BEST_SPEED;
 int pack_compression_level = Z_DEFAULT_COMPRESSION;
 int fsync_object_files = -1;
@@ -363,11 +362,6 @@ static int git_default_core_config(const char *var, const char *value,
 		return 0;
 	}
 
-	if (!strcmp(var, "core.attributesfile")) {
-		FREE_AND_NULL(git_attributes_file);
-		return git_config_pathname(&git_attributes_file, var, value);
-	}
-
 	if (!strcmp(var, "core.bare")) {
 		is_bare_repository_cfg = git_config_bool(var, value);
 		return 0;
diff --git a/environment.h b/environment.h
index 51898c99cd..3512a7072e 100644
--- a/environment.h
+++ b/environment.h
@@ -152,7 +152,6 @@ extern int assume_unchanged;
 extern int warn_on_object_refname_ambiguity;
 extern char *apply_default_whitespace;
 extern char *apply_default_ignorewhitespace;
-extern char *git_attributes_file;
 extern int zlib_compression_level;
 extern int pack_compression_level;
 extern unsigned long pack_size_limit_cfg;
diff --git a/repo-settings.c b/repo-settings.c
index 195c24e9c0..396cf79f20 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -5,6 +5,7 @@
 #include "midx.h"
 #include "pack-objects.h"
 #include "setup.h"
+#include "path.h"
 
 static void repo_cfg_bool(struct repository *r, const char *key, int *dest,
 			  int def)
@@ -158,6 +159,7 @@ void repo_settings_clear(struct repository *r)
 	struct repo_settings empty = REPO_SETTINGS_INIT;
 	FREE_AND_NULL(r->settings.fsmonitor);
 	FREE_AND_NULL(r->settings.hooks_path);
+	FREE_AND_NULL(r->settings.git_attributes_file);
 	r->settings = empty;
 }
 
@@ -230,3 +232,11 @@ void repo_settings_reset_shared_repository(struct repository *repo)
 {
 	repo->settings.shared_repository_initialized = 0;
 }
+const char *repo_settings_get_attributesfile_path(struct repository *repo)
+{
+	if (!repo->settings.git_attributes_file) {
+		if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.git_attributes_file))
+			repo->settings.git_attributes_file = xdg_config_home("attributes");
+	}
+	return repo->settings.git_attributes_file;
+}
diff --git a/repo-settings.h b/repo-settings.h
index d477885561..362f355267 100644
--- a/repo-settings.h
+++ b/repo-settings.h
@@ -68,6 +68,7 @@ struct repo_settings {
 	unsigned long big_file_threshold;
 
 	char *hooks_path;
+	char *git_attributes_file;
 };
 #define REPO_SETTINGS_INIT { \
 	.shared_repository = -1, \
@@ -99,4 +100,11 @@ int repo_settings_get_shared_repository(struct repository *repo);
 void repo_settings_set_shared_repository(struct repository *repo, int value);
 void repo_settings_reset_shared_repository(struct repository *repo);
 
+/*
+ * Read the value for "core.attributesfile".
+ * Defaults to xdg_config_home("attributes") if the core.attributesfile
+ * isn't available.
+ */
+const char *repo_settings_get_attributesfile_path(struct repository *repo);
+
 #endif /* REPO_SETTINGS_H */
-- 
2.34.1
Bello Olamide· Dec 18, 2025, 17:31 UTC · re: Olamide Caleb Bello · lore

Re: [Outreachy PATCH] environment: move "core.attributesFile" into repo-setting

On Thu, 18 Dec 2025 at 09:30, Olamide Caleb Bello <belkid98@gmail.com> wrote:
Show 28 quoted lines
>
> When handling multiple repositories within the same process, relying on
> global state for accessing the "core.attributesFile" configuration can
> lead to incorrect values being used. It also makes it harder to isolate
> repositories and hinders the libification of git.
> The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
> retrieve "core.attributesFile" via `git_attr_global_file()`
> which reads from global state `git_attributes_file`.
>
> Move the "core.attributesFile" configuration into the
> `struct repo_settings` instead of relying on the global state.
> A new function `repo_settings_get_attributesfile_path()` is added
> and used to retrieve this setting in a repository-scoped manner.
> The functions to retrieve "core.attributesFile" are replaced with
> the new accessor function `repo_settings_get_attributesfile_path()`
> This improves multi-repository behaviour and aligns with the goal of
> libifying of Git.
>
> Note that in `bootstrap_attr_stack()`, the `index_state` is used only
> if it exists, else we default to `the_repository`.
>
> Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
> Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
> ---
> The link to the GitHub CI is provided below
> https://github.com/cloobTech/git/actions/runs/20284228144

The link to Ayush's patches, which this patch is based on, is provided in [1]. The 'git_attributes_file' member of `struct repository` is now accessed via the `struct index_state`, istate->repo, as most of the callers in the attributes subsystem already use the `index_state`, rather than through the `struct repository *repo` as done in [1] which only knows its primary index. This ensures that the index actually owns the attributes as pointed out by Junio in the threads.

[1]. https://lore.kernel.org/git/20250309153321.254844-1-ayu.chandekar@gmail.com/
Show 177 quoted lines
>
>  attr.c          | 20 +++++++++-----------
>  attr.h          |  3 ---
>  builtin/var.c   |  2 +-
>  environment.c   |  6 ------
>  environment.h   |  1 -
>  repo-settings.c | 10 ++++++++++
>  repo-settings.h |  8 ++++++++
>  7 files changed, 28 insertions(+), 22 deletions(-)
>
> diff --git a/attr.c b/attr.c
> index 4999b7e09d..9e51f8e70b 100644
> --- a/attr.c
> +++ b/attr.c
> @@ -879,14 +879,6 @@ const char *git_attr_system_file(void)
>         return system_wide;
>  }
>
> -const char *git_attr_global_file(void)
> -{
> -       if (!git_attributes_file)
> -               git_attributes_file = xdg_config_home("attributes");
> -
> -       return git_attributes_file;
> -}
> -
>  int git_attr_system_is_enabled(void)
>  {
>         return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
> @@ -912,6 +904,8 @@ static void bootstrap_attr_stack(struct index_state *istate,
>  {
>         struct attr_stack *e;
>         unsigned flags = READ_ATTR_MACRO_OK;
> +       const char *attributes_file_path;
> +       struct repository *repo;
>
>         if (*stack)
>                 return;
> @@ -926,9 +920,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
>                 push_stack(stack, e, NULL, 0);
>         }
>
> -       /* home directory */
> -       if (git_attr_global_file()) {
> -               e = read_attr_from_file(git_attr_global_file(), flags);
> +       if (istate && istate->repo)
> +               repo = istate->repo;
> +       else
> +               repo = the_repository;
> +       attributes_file_path = repo_settings_get_attributesfile_path(repo);
> +       if (attributes_file_path) {
> +               e = read_attr_from_file(attributes_file_path, flags);
>                 push_stack(stack, e, NULL, 0);
>         }
>
> diff --git a/attr.h b/attr.h
> index a04a521092..956ce6ba62 100644
> --- a/attr.h
> +++ b/attr.h
> @@ -232,9 +232,6 @@ void attr_start(void);
>  /* Return the system gitattributes file. */
>  const char *git_attr_system_file(void);
>
> -/* Return the global gitattributes file, if any. */
> -const char *git_attr_global_file(void);
> -
>  /* Return whether the system gitattributes file is enabled and should be used. */
>  int git_attr_system_is_enabled(void);
>
> diff --git a/builtin/var.c b/builtin/var.c
> index cc3a43cde2..fd577f2930 100644
> --- a/builtin/var.c
> +++ b/builtin/var.c
> @@ -72,7 +72,7 @@ static char *git_attr_val_system(int ident_flag UNUSED)
>
>  static char *git_attr_val_global(int ident_flag UNUSED)
>  {
> -       char *file = xstrdup_or_null(git_attr_global_file());
> +       char *file = xstrdup_or_null(repo_settings_get_attributesfile_path(the_repository));
>         if (file) {
>                 normalize_path_copy(file, file);
>                 return file;
> diff --git a/environment.c b/environment.c
> index a770b5921d..ed7d8f42d9 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -53,7 +53,6 @@ char *git_commit_encoding;
>  char *git_log_output_encoding;
>  char *apply_default_whitespace;
>  char *apply_default_ignorewhitespace;
> -char *git_attributes_file;
>  int zlib_compression_level = Z_BEST_SPEED;
>  int pack_compression_level = Z_DEFAULT_COMPRESSION;
>  int fsync_object_files = -1;
> @@ -363,11 +362,6 @@ static int git_default_core_config(const char *var, const char *value,
>                 return 0;
>         }
>
> -       if (!strcmp(var, "core.attributesfile")) {
> -               FREE_AND_NULL(git_attributes_file);
> -               return git_config_pathname(&git_attributes_file, var, value);
> -       }
> -
>         if (!strcmp(var, "core.bare")) {
>                 is_bare_repository_cfg = git_config_bool(var, value);
>                 return 0;
> diff --git a/environment.h b/environment.h
> index 51898c99cd..3512a7072e 100644
> --- a/environment.h
> +++ b/environment.h
> @@ -152,7 +152,6 @@ extern int assume_unchanged;
>  extern int warn_on_object_refname_ambiguity;
>  extern char *apply_default_whitespace;
>  extern char *apply_default_ignorewhitespace;
> -extern char *git_attributes_file;
>  extern int zlib_compression_level;
>  extern int pack_compression_level;
>  extern unsigned long pack_size_limit_cfg;
> diff --git a/repo-settings.c b/repo-settings.c
> index 195c24e9c0..396cf79f20 100644
> --- a/repo-settings.c
> +++ b/repo-settings.c
> @@ -5,6 +5,7 @@
>  #include "midx.h"
>  #include "pack-objects.h"
>  #include "setup.h"
> +#include "path.h"
>
>  static void repo_cfg_bool(struct repository *r, const char *key, int *dest,
>                           int def)
> @@ -158,6 +159,7 @@ void repo_settings_clear(struct repository *r)
>         struct repo_settings empty = REPO_SETTINGS_INIT;
>         FREE_AND_NULL(r->settings.fsmonitor);
>         FREE_AND_NULL(r->settings.hooks_path);
> +       FREE_AND_NULL(r->settings.git_attributes_file);
>         r->settings = empty;
>  }
>
> @@ -230,3 +232,11 @@ void repo_settings_reset_shared_repository(struct repository *repo)
>  {
>         repo->settings.shared_repository_initialized = 0;
>  }
> +const char *repo_settings_get_attributesfile_path(struct repository *repo)
> +{
> +       if (!repo->settings.git_attributes_file) {
> +               if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.git_attributes_file))
> +                       repo->settings.git_attributes_file = xdg_config_home("attributes");
> +       }
> +       return repo->settings.git_attributes_file;
> +}
> diff --git a/repo-settings.h b/repo-settings.h
> index d477885561..362f355267 100644
> --- a/repo-settings.h
> +++ b/repo-settings.h
> @@ -68,6 +68,7 @@ struct repo_settings {
>         unsigned long big_file_threshold;
>
>         char *hooks_path;
> +       char *git_attributes_file;
>  };
>  #define REPO_SETTINGS_INIT { \
>         .shared_repository = -1, \
> @@ -99,4 +100,11 @@ int repo_settings_get_shared_repository(struct repository *repo);
>  void repo_settings_set_shared_repository(struct repository *repo, int value);
>  void repo_settings_reset_shared_repository(struct repository *repo);
>
> +/*
> + * Read the value for "core.attributesfile".
> + * Defaults to xdg_config_home("attributes") if the core.attributesfile
> + * isn't available.
> + */
> +const char *repo_settings_get_attributesfile_path(struct repository *repo);
> +
>  #endif /* REPO_SETTINGS_H */
> --
> 2.34.1
>
Bello Olamide· Jan 2, 2026, 08:01 UTC · re: Olamide Caleb Bello · lore

Re: [Outreachy PATCH] environment: move "core.attributesFile" into repo-setting

On Thu, 18 Dec 2025 at 09:30, Olamide Caleb Bello <belkid98@gmail.com> wrote:
Show 25 quoted lines
>
> When handling multiple repositories within the same process, relying on
> global state for accessing the "core.attributesFile" configuration can
> lead to incorrect values being used. It also makes it harder to isolate
> repositories and hinders the libification of git.
> The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
> retrieve "core.attributesFile" via `git_attr_global_file()`
> which reads from global state `git_attributes_file`.
>
> Move the "core.attributesFile" configuration into the
> `struct repo_settings` instead of relying on the global state.
> A new function `repo_settings_get_attributesfile_path()` is added
> and used to retrieve this setting in a repository-scoped manner.
> The functions to retrieve "core.attributesFile" are replaced with
> the new accessor function `repo_settings_get_attributesfile_path()`
> This improves multi-repository behaviour and aligns with the goal of
> libifying of Git.
>
> Note that in `bootstrap_attr_stack()`, the `index_state` is used only
> if it exists, else we default to `the_repository`.
>
> Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
> Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>

Hello. Please I am replying to this as no reviews have been done on this patch. Thanks [...]

Karthik Nayak· Jan 2, 2026, 08:49 UTC · re: Bello Olamide · lore

Re: [Outreachy PATCH] environment: move "core.attributesFile" into repo-setting

Bello Olamide <belkid98@gmail.com> writes:
Show 30 quoted lines
> On Thu, 18 Dec 2025 at 09:30, Olamide Caleb Bello <belkid98@gmail.com> wrote:
>>
>> When handling multiple repositories within the same process, relying on
>> global state for accessing the "core.attributesFile" configuration can
>> lead to incorrect values being used. It also makes it harder to isolate
>> repositories and hinders the libification of git.
>> The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
>> retrieve "core.attributesFile" via `git_attr_global_file()`
>> which reads from global state `git_attributes_file`.
>>
>> Move the "core.attributesFile" configuration into the
>> `struct repo_settings` instead of relying on the global state.
>> A new function `repo_settings_get_attributesfile_path()` is added
>> and used to retrieve this setting in a repository-scoped manner.
>> The functions to retrieve "core.attributesFile" are replaced with
>> the new accessor function `repo_settings_get_attributesfile_path()`
>> This improves multi-repository behaviour and aligns with the goal of
>> libifying of Git.
>>
>> Note that in `bootstrap_attr_stack()`, the `index_state` is used only
>> if it exists, else we default to `the_repository`.
>>
>> Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
>> Mentored-by: Christian Couder <christian.couder@gmail.com>
>> Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
>> Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
>
> Hello.
> Please I am replying to this as no reviews have been done on this patch.
> Thanks

Thanks for the bump, I think reviews are slowed down due to holidays. You should see a uptick henceforth :)

Karthik Nayak· Jan 2, 2026, 08:48 UTC · re: Olamide Caleb Bello · lore

Re: [Outreachy PATCH] environment: move "core.attributesFile" into repo-setting

Olamide Caleb Bello <belkid98@gmail.com> writes:
Show 36 quoted lines
> When handling multiple repositories within the same process, relying on
> global state for accessing the "core.attributesFile" configuration can
> lead to incorrect values being used. It also makes it harder to isolate
> repositories and hinders the libification of git.
> The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
> retrieve "core.attributesFile" via `git_attr_global_file()`
> which reads from global state `git_attributes_file`.
>
> Move the "core.attributesFile" configuration into the
> `struct repo_settings` instead of relying on the global state.
> A new function `repo_settings_get_attributesfile_path()` is added
> and used to retrieve this setting in a repository-scoped manner.
> The functions to retrieve "core.attributesFile" are replaced with
> the new accessor function `repo_settings_get_attributesfile_path()`
> This improves multi-repository behaviour and aligns with the goal of
> libifying of Git.
>
> Note that in `bootstrap_attr_stack()`, the `index_state` is used only
> if it exists, else we default to `the_repository`.
>
> Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
> Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
> ---
> The link to the GitHub CI is provided below
> https://github.com/cloobTech/git/actions/runs/20284228144
>
>  attr.c          | 20 +++++++++-----------
>  attr.h          |  3 ---
>  builtin/var.c   |  2 +-
>  environment.c   |  6 ------
>  environment.h   |  1 -
>  repo-settings.c | 10 ++++++++++
>  repo-settings.h |  8 ++++++++
>  7 files changed, 28 insertions(+), 22 deletions(-)

The change is very welcome. Apart from some small comments below, the patch looks good.

[snip]
Show 12 quoted lines
> diff --git a/repo-settings.h b/repo-settings.h
> index d477885561..362f355267 100644
> --- a/repo-settings.h
> +++ b/repo-settings.h
> @@ -68,6 +68,7 @@ struct repo_settings {
>  	unsigned long big_file_threshold;
>
>  	char *hooks_path;
> +	char *git_attributes_file;
>  };
>  #define REPO_SETTINGS_INIT { \
>  	.shared_repository = -1, \

It would make more sense to rename this variable to `attributes_file_path`, that would better denote what is actually stored here and syncs better with `repo_settings_get_attributesfile_path`.

Show 8 quoted lines
> @@ -99,4 +100,11 @@ int repo_settings_get_shared_repository(struct repository *repo);
>  void repo_settings_set_shared_repository(struct repository *repo, int value);
>  void repo_settings_reset_shared_repository(struct repository *repo);
>
> +/*
> + * Read the value for "core.attributesfile".
> + * Defaults to xdg_config_home("attributes") if the core.attributesfile
> + * isn't available.

While it is obvious, it would be nice to point out that `core.attributesfile` is set via config.

Show 6 quoted lines
> + */
> +const char *repo_settings_get_attributesfile_path(struct repository *repo);
> +
>  #endif /* REPO_SETTINGS_H */
> --
> 2.34.1
Bello Olamide· Jan 2, 2026, 11:26 UTC · re: Karthik Nayak · lore

Re: [Outreachy PATCH] environment: move "core.attributesFile" into repo-setting

On Fri, 2 Jan 2026 at 09:48, Karthik Nayak <karthik.188@gmail.com> wrote:
Show 74 quoted lines
>
> Olamide Caleb Bello <belkid98@gmail.com> writes:
>
> > When handling multiple repositories within the same process, relying on
> > global state for accessing the "core.attributesFile" configuration can
> > lead to incorrect values being used. It also makes it harder to isolate
> > repositories and hinders the libification of git.
> > The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
> > retrieve "core.attributesFile" via `git_attr_global_file()`
> > which reads from global state `git_attributes_file`.
> >
> > Move the "core.attributesFile" configuration into the
> > `struct repo_settings` instead of relying on the global state.
> > A new function `repo_settings_get_attributesfile_path()` is added
> > and used to retrieve this setting in a repository-scoped manner.
> > The functions to retrieve "core.attributesFile" are replaced with
> > the new accessor function `repo_settings_get_attributesfile_path()`
> > This improves multi-repository behaviour and aligns with the goal of
> > libifying of Git.
> >
> > Note that in `bootstrap_attr_stack()`, the `index_state` is used only
> > if it exists, else we default to `the_repository`.
> >
> > Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
> > Mentored-by: Christian Couder <christian.couder@gmail.com>
> > Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
> > Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
> > ---
> > The link to the GitHub CI is provided below
> > https://github.com/cloobTech/git/actions/runs/20284228144
> >
> >  attr.c          | 20 +++++++++-----------
> >  attr.h          |  3 ---
> >  builtin/var.c   |  2 +-
> >  environment.c   |  6 ------
> >  environment.h   |  1 -
> >  repo-settings.c | 10 ++++++++++
> >  repo-settings.h |  8 ++++++++
> >  7 files changed, 28 insertions(+), 22 deletions(-)
>
> The change is very welcome. Apart from some small comments below, the
> patch looks good.
>
> [snip]
>
> > diff --git a/repo-settings.h b/repo-settings.h
> > index d477885561..362f355267 100644
> > --- a/repo-settings.h
> > +++ b/repo-settings.h
> > @@ -68,6 +68,7 @@ struct repo_settings {
> >       unsigned long big_file_threshold;
> >
> >       char *hooks_path;
> > +     char *git_attributes_file;
> >  };
> >  #define REPO_SETTINGS_INIT { \
> >       .shared_repository = -1, \
>
> It would make more sense to rename this variable to
> `attributes_file_path`, that would better denote what is actually stored
> here and syncs better with `repo_settings_get_attributesfile_path`.
>
> > @@ -99,4 +100,11 @@ int repo_settings_get_shared_repository(struct repository *repo);
> >  void repo_settings_set_shared_repository(struct repository *repo, int value);
> >  void repo_settings_reset_shared_repository(struct repository *repo);
> >
> > +/*
> > + * Read the value for "core.attributesfile".
> > + * Defaults to xdg_config_home("attributes") if the core.attributesfile
> > + * isn't available.
>
> While it is obvious, it would be nice to point out that
> `core.attributesfile` is set via config.
>

Thank you for the review Karthik. I will send an updated version with the changes.

Bello.
Olamide Caleb Bello· Jan 2, 2026, 16:32 UTC · re: Olamide Caleb Bello · lore

[Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

When handling multiple repositories within the same process, relying on global state for accessing the "core.attributesFile" configuration can lead to incorrect values being used. It also makes it harder to isolate repositories and hinders the libification of git. The functions `bootstrap_attr_stack()` and `git_attr_val_system()` retrieve "core.attributesFile" via `git_attr_global_file()` which reads from global state `git_attributes_file`.

Move the "core.attributesFile" configuration into the `struct repo_settings` instead of relying on the global state. A new function `repo_settings_get_attributesfile_path()` is added and used to retrieve this setting in a repository-scoped manner. The functions to retrieve "core.attributesFile" are replaced with the new accessor function `repo_settings_get_attributesfile_path()` This improves multi-repository behaviour and aligns with the goal of libifying of Git.

Note that in `bootstrap_attr_stack()`, the `index_state` is used only if it exists, else we default to `the_repository`.

Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
Mentored-by: Christian Couder <christian.couder@gmail.com>
Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
---
The link to the GitHub CI is provided below
https://github.com/git/git/actions/runs/20661817110
Changes in v2:
--------------
- Renamed the variable in the repo-settings struct to `attributes_file_path`
- Modified the comment section of the accessor function declaration to
  indicate the core.attributesFile is read via repo config.
Range diff vs v1:
-----------------
1:  4975f77cde ! 1:  fc1dbec892 environment: move "core.attributesfile" into repo-setting
    @@ Commit message
         Note that in `bootstrap_attr_stack()`, the `index_state` is used only
         if it exists, else we default to `the_repository`.
     
    -    Reported-by: Ayush Chandekar <ayu.chandekar@gmail.com>
    +    Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
         Mentored-by: Christian Couder <christian.couder@gmail.com>
         Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
         Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
    @@ attr.c: static void bootstrap_attr_stack(struct index_state *istate,
      	if (*stack)
      		return;
     @@ attr.c: static void bootstrap_attr_stack(struct index_state *istate,
    - 		push_stack(stack, e, NULL, 0);
      	}
      
    --	/* home directory */
    + 	/* home directory */
     -	if (git_attr_global_file()) {
     -		e = read_attr_from_file(git_attr_global_file(), flags);
     +	if (istate && istate->repo)
    @@ repo-settings.c: void repo_settings_clear(struct repository *r)
      	struct repo_settings empty = REPO_SETTINGS_INIT;
      	FREE_AND_NULL(r->settings.fsmonitor);
      	FREE_AND_NULL(r->settings.hooks_path);
    -+	FREE_AND_NULL(r->settings.git_attributes_file);
    ++	FREE_AND_NULL(r->settings.attributes_file_path);
      	r->settings = empty;
      }
      
    @@ repo-settings.c: void repo_settings_reset_shared_repository(struct repository *r
      }
     +const char *repo_settings_get_attributesfile_path(struct repository *repo)
     +{
    -+	if (!repo->settings.git_attributes_file) {
    -+		if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.git_attributes_file))
    -+			repo->settings.git_attributes_file = xdg_config_home("attributes");
    ++	if (!repo->settings.attributes_file_path) {
    ++		if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.attributes_file_path))
    ++			repo->settings.attributes_file_path = xdg_config_home("attributes");
     +	}
    -+	return repo->settings.git_attributes_file;
    ++	return repo->settings.attributes_file_path;
     +}
     
      ## repo-settings.h ##
    @@ repo-settings.h: struct repo_settings {
      	unsigned long big_file_threshold;
      
      	char *hooks_path;
    -+	char *git_attributes_file;
    ++	char *attributes_file_path;
      };
      #define REPO_SETTINGS_INIT { \
      	.shared_repository = -1, \
    @@ repo-settings.h: int repo_settings_get_shared_repository(struct repository *repo
     +/*
     + * Read the value for "core.attributesfile".
     + * Defaults to xdg_config_home("attributes") if the core.attributesfile
    -+ * isn't available.
    ++ * which is set via repo config isn't available.
     + */
     +const char *repo_settings_get_attributesfile_path(struct repository *repo);
     +
 attr.c          | 19 +++++++++----------
 attr.h          |  3 ---
 builtin/var.c   |  2 +-
 environment.c   |  6 ------
 environment.h   |  1 -
 repo-settings.c | 10 ++++++++++
 repo-settings.h |  8 ++++++++
 7 files changed, 28 insertions(+), 21 deletions(-)
Show changes to 7 files +28 −21

attr.c, attr.h, builtin/var.c, environment.c, environment.h, repo-settings.c, repo-settings.h

diff --git a/attr.c b/attr.c
index 4999b7e09d..b081400c18 100644
--- a/attr.c
+++ b/attr.c
@@ -879,14 +879,6 @@ const char *git_attr_system_file(void)
 	return system_wide;
 }
 
-const char *git_attr_global_file(void)
-{
-	if (!git_attributes_file)
-		git_attributes_file = xdg_config_home("attributes");
-
-	return git_attributes_file;
-}
-
 int git_attr_system_is_enabled(void)
 {
 	return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
@@ -912,6 +904,8 @@ static void bootstrap_attr_stack(struct index_state *istate,
 {
 	struct attr_stack *e;
 	unsigned flags = READ_ATTR_MACRO_OK;
+	const char *attributes_file_path;
+	struct repository *repo;
 
 	if (*stack)
 		return;
@@ -927,8 +921,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
 	}
 
 	/* home directory */
-	if (git_attr_global_file()) {
-		e = read_attr_from_file(git_attr_global_file(), flags);
+	if (istate && istate->repo)
+		repo = istate->repo;
+	else
+		repo = the_repository;
+	attributes_file_path = repo_settings_get_attributesfile_path(repo);
+	if (attributes_file_path) {
+		e = read_attr_from_file(attributes_file_path, flags);
 		push_stack(stack, e, NULL, 0);
 	}
 
diff --git a/attr.h b/attr.h
index a04a521092..956ce6ba62 100644
--- a/attr.h
+++ b/attr.h
@@ -232,9 +232,6 @@ void attr_start(void);
 /* Return the system gitattributes file. */
 const char *git_attr_system_file(void);
 
-/* Return the global gitattributes file, if any. */
-const char *git_attr_global_file(void);
-
 /* Return whether the system gitattributes file is enabled and should be used. */
 int git_attr_system_is_enabled(void);
 
diff --git a/builtin/var.c b/builtin/var.c
index cc3a43cde2..fd577f2930 100644
--- a/builtin/var.c
+++ b/builtin/var.c
@@ -72,7 +72,7 @@ static char *git_attr_val_system(int ident_flag UNUSED)
 
 static char *git_attr_val_global(int ident_flag UNUSED)
 {
-	char *file = xstrdup_or_null(git_attr_global_file());
+	char *file = xstrdup_or_null(repo_settings_get_attributesfile_path(the_repository));
 	if (file) {
 		normalize_path_copy(file, file);
 		return file;
diff --git a/environment.c b/environment.c
index a770b5921d..ed7d8f42d9 100644
--- a/environment.c
+++ b/environment.c
@@ -53,7 +53,6 @@ char *git_commit_encoding;
 char *git_log_output_encoding;
 char *apply_default_whitespace;
 char *apply_default_ignorewhitespace;
-char *git_attributes_file;
 int zlib_compression_level = Z_BEST_SPEED;
 int pack_compression_level = Z_DEFAULT_COMPRESSION;
 int fsync_object_files = -1;
@@ -363,11 +362,6 @@ static int git_default_core_config(const char *var, const char *value,
 		return 0;
 	}
 
-	if (!strcmp(var, "core.attributesfile")) {
-		FREE_AND_NULL(git_attributes_file);
-		return git_config_pathname(&git_attributes_file, var, value);
-	}
-
 	if (!strcmp(var, "core.bare")) {
 		is_bare_repository_cfg = git_config_bool(var, value);
 		return 0;
diff --git a/environment.h b/environment.h
index 51898c99cd..3512a7072e 100644
--- a/environment.h
+++ b/environment.h
@@ -152,7 +152,6 @@ extern int assume_unchanged;
 extern int warn_on_object_refname_ambiguity;
 extern char *apply_default_whitespace;
 extern char *apply_default_ignorewhitespace;
-extern char *git_attributes_file;
 extern int zlib_compression_level;
 extern int pack_compression_level;
 extern unsigned long pack_size_limit_cfg;
diff --git a/repo-settings.c b/repo-settings.c
index 195c24e9c0..cc53a3cd3b 100644
--- a/repo-settings.c
+++ b/repo-settings.c
@@ -5,6 +5,7 @@
 #include "midx.h"
 #include "pack-objects.h"
 #include "setup.h"
+#include "path.h"
 
 static void repo_cfg_bool(struct repository *r, const char *key, int *dest,
 			  int def)
@@ -158,6 +159,7 @@ void repo_settings_clear(struct repository *r)
 	struct repo_settings empty = REPO_SETTINGS_INIT;
 	FREE_AND_NULL(r->settings.fsmonitor);
 	FREE_AND_NULL(r->settings.hooks_path);
+	FREE_AND_NULL(r->settings.attributes_file_path);
 	r->settings = empty;
 }
 
@@ -230,3 +232,11 @@ void repo_settings_reset_shared_repository(struct repository *repo)
 {
 	repo->settings.shared_repository_initialized = 0;
 }
+const char *repo_settings_get_attributesfile_path(struct repository *repo)
+{
+	if (!repo->settings.attributes_file_path) {
+		if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.attributes_file_path))
+			repo->settings.attributes_file_path = xdg_config_home("attributes");
+	}
+	return repo->settings.attributes_file_path;
+}
diff --git a/repo-settings.h b/repo-settings.h
index d477885561..1209e1db83 100644
--- a/repo-settings.h
+++ b/repo-settings.h
@@ -68,6 +68,7 @@ struct repo_settings {
 	unsigned long big_file_threshold;
 
 	char *hooks_path;
+	char *attributes_file_path;
 };
 #define REPO_SETTINGS_INIT { \
 	.shared_repository = -1, \
@@ -99,4 +100,11 @@ int repo_settings_get_shared_repository(struct repository *repo);
 void repo_settings_set_shared_repository(struct repository *repo, int value);
 void repo_settings_reset_shared_repository(struct repository *repo);
 
+/*
+ * Read the value for "core.attributesfile".
+ * Defaults to xdg_config_home("attributes") if the core.attributesfile
+ * which is set via repo config isn't available.
+ */
+const char *repo_settings_get_attributesfile_path(struct repository *repo);
+
 #endif /* REPO_SETTINGS_H */
-- 
2.52.0.210.g7f9f2609ac.dirty
Karthik Nayak· Jan 5, 2026, 11:09 UTC · re: Olamide Caleb Bello · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

Olamide Caleb Bello <belkid98@gmail.com> writes: [snip]

Show 16 quoted lines
> @@ -927,8 +921,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
>  	}
>
>  	/* home directory */
> -	if (git_attr_global_file()) {
> -		e = read_attr_from_file(git_attr_global_file(), flags);
> +	if (istate && istate->repo)
> +		repo = istate->repo;
> +	else
> +		repo = the_repository;
> +	attributes_file_path = repo_settings_get_attributesfile_path(repo);
> +	if (attributes_file_path) {
> +		e = read_attr_from_file(attributes_file_path, flags);
>  		push_stack(stack, e, NULL, 0);
>  	}
>
For my own understanding, when can `istate` be NULL?
[snip]
Bello Olamide· Jan 5, 2026, 11:39 UTC · re: Karthik Nayak · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On Mon, 5 Jan 2026 at 12:09, Karthik Nayak <karthik.188@gmail.com> wrote:
Show 23 quoted lines
>
> Olamide Caleb Bello <belkid98@gmail.com> writes:
> [snip]
>
> > @@ -927,8 +921,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
> >       }
> >
> >       /* home directory */
> > -     if (git_attr_global_file()) {
> > -             e = read_attr_from_file(git_attr_global_file(), flags);
> > +     if (istate && istate->repo)
> > +             repo = istate->repo;
> > +     else
> > +             repo = the_repository;
> > +     attributes_file_path = repo_settings_get_attributesfile_path(repo);
> > +     if (attributes_file_path) {
> > +             e = read_attr_from_file(attributes_file_path, flags);
> >               push_stack(stack, e, NULL, 0);
> >       }
> >
>
> For my own understanding, when can `istate` be NULL?
>

Thank you for your question Karthik. So it was stated in a comment in `apply.c:read_old_data():2340` that `git apply without --index/cached should never look at the index because the target file may not be in the index yet and we may not be in a Git repository.` So NULL is passed to convert_to_git() in place of `istate`.

So when we do `git apply patch.file` `istate` is NULL, but when we do `git apply --cached patch.file`, `istate` is not NULL.

Bello
Phillip Wood· Jan 5, 2026, 14:23 UTC · re: Olamide Caleb Bello · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

Hi Olamide
On 02/01/2026 16:32, Olamide Caleb Bello wrote:
Show 10 quoted lines
> When handling multiple repositories within the same process, relying on
> global state for accessing the "core.attributesFile" configuration can
> lead to incorrect values being used. It also makes it harder to isolate
> repositories and hinders the libification of git.
> The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
> retrieve "core.attributesFile" via `git_attr_global_file()`
> which reads from global state `git_attributes_file`.
> 
> Move the "core.attributesFile" configuration into the
> `struct repo_settings` instead of relying on the global state.

This changes when the config setting gets parsed which unfortunately regresses the user experience when the setting is invalid.

If I run 'git -c core.attributesFile=~does-not-exist rebase -i' with git built from master it fails immediately with "fatal: failed to expand user dir in: '~does-not-exist'". With this patch applied it prompts me to edit the todo list and then fails when it tries to checkout the commit we're rebasing onto. Because "git rebase" expects reset_head() to return an error rather die if the checkout fails it is left in a strange state where only practical course of action for the user is to run "git rebase --abort".

It is quite common that moving from parsing config settings eagerly by calling repo_config() at startup to parsing them lazily via 'stuct repo_settings' causes regressions like this. We really should find a way to address that before moving more settings into 'struct repo_settings'

Thanks
Phillip
Show 264 quoted lines
> A new function `repo_settings_get_attributesfile_path()` is added
> and used to retrieve this setting in a repository-scoped manner.
> The functions to retrieve "core.attributesFile" are replaced with
> the new accessor function `repo_settings_get_attributesfile_path()`
> This improves multi-repository behaviour and aligns with the goal of
> libifying of Git.
> 
> Note that in `bootstrap_attr_stack()`, the `index_state` is used only
> if it exists, else we default to `the_repository`.
> 
> Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
> Mentored-by: Christian Couder <christian.couder@gmail.com>
> Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
> Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
> ---
> The link to the GitHub CI is provided below
> https://github.com/git/git/actions/runs/20661817110
> 
> Changes in v2:
> --------------
> - Renamed the variable in the repo-settings struct to `attributes_file_path`
> - Modified the comment section of the accessor function declaration to
>    indicate the core.attributesFile is read via repo config.
> 
> Range diff vs v1:
> -----------------
> 1:  4975f77cde ! 1:  fc1dbec892 environment: move "core.attributesfile" into repo-setting
>      @@ Commit message
>           Note that in `bootstrap_attr_stack()`, the `index_state` is used only
>           if it exists, else we default to `the_repository`.
>       
>      -    Reported-by: Ayush Chandekar <ayu.chandekar@gmail.com>
>      +    Based-on-patch-by: Ayush Chandekar <ayu.chandekar@gmail.com>
>           Mentored-by: Christian Couder <christian.couder@gmail.com>
>           Mentored-by: Usman Akinyemi <usmanakinyemi202@gmail.com>
>           Signed-off-by: Olamide Caleb Bello <belkid98@gmail.com>
>      @@ attr.c: static void bootstrap_attr_stack(struct index_state *istate,
>        	if (*stack)
>        		return;
>       @@ attr.c: static void bootstrap_attr_stack(struct index_state *istate,
>      - 		push_stack(stack, e, NULL, 0);
>        	}
>        
>      --	/* home directory */
>      + 	/* home directory */
>       -	if (git_attr_global_file()) {
>       -		e = read_attr_from_file(git_attr_global_file(), flags);
>       +	if (istate && istate->repo)
>      @@ repo-settings.c: void repo_settings_clear(struct repository *r)
>        	struct repo_settings empty = REPO_SETTINGS_INIT;
>        	FREE_AND_NULL(r->settings.fsmonitor);
>        	FREE_AND_NULL(r->settings.hooks_path);
>      -+	FREE_AND_NULL(r->settings.git_attributes_file);
>      ++	FREE_AND_NULL(r->settings.attributes_file_path);
>        	r->settings = empty;
>        }
>        
>      @@ repo-settings.c: void repo_settings_reset_shared_repository(struct repository *r
>        }
>       +const char *repo_settings_get_attributesfile_path(struct repository *repo)
>       +{
>      -+	if (!repo->settings.git_attributes_file) {
>      -+		if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.git_attributes_file))
>      -+			repo->settings.git_attributes_file = xdg_config_home("attributes");
>      ++	if (!repo->settings.attributes_file_path) {
>      ++		if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.attributes_file_path))
>      ++			repo->settings.attributes_file_path = xdg_config_home("attributes");
>       +	}
>      -+	return repo->settings.git_attributes_file;
>      ++	return repo->settings.attributes_file_path;
>       +}
>       
>        ## repo-settings.h ##
>      @@ repo-settings.h: struct repo_settings {
>        	unsigned long big_file_threshold;
>        
>        	char *hooks_path;
>      -+	char *git_attributes_file;
>      ++	char *attributes_file_path;
>        };
>        #define REPO_SETTINGS_INIT { \
>        	.shared_repository = -1, \
>      @@ repo-settings.h: int repo_settings_get_shared_repository(struct repository *repo
>       +/*
>       + * Read the value for "core.attributesfile".
>       + * Defaults to xdg_config_home("attributes") if the core.attributesfile
>      -+ * isn't available.
>      ++ * which is set via repo config isn't available.
>       + */
>       +const char *repo_settings_get_attributesfile_path(struct repository *repo);
>       +
> 
>   attr.c          | 19 +++++++++----------
>   attr.h          |  3 ---
>   builtin/var.c   |  2 +-
>   environment.c   |  6 ------
>   environment.h   |  1 -
>   repo-settings.c | 10 ++++++++++
>   repo-settings.h |  8 ++++++++
>   7 files changed, 28 insertions(+), 21 deletions(-)
> 
> diff --git a/attr.c b/attr.c
> index 4999b7e09d..b081400c18 100644
> --- a/attr.c
> +++ b/attr.c
> @@ -879,14 +879,6 @@ const char *git_attr_system_file(void)
>   	return system_wide;
>   }
>   
> -const char *git_attr_global_file(void)
> -{
> -	if (!git_attributes_file)
> -		git_attributes_file = xdg_config_home("attributes");
> -
> -	return git_attributes_file;
> -}
> -
>   int git_attr_system_is_enabled(void)
>   {
>   	return !git_env_bool("GIT_ATTR_NOSYSTEM", 0);
> @@ -912,6 +904,8 @@ static void bootstrap_attr_stack(struct index_state *istate,
>   {
>   	struct attr_stack *e;
>   	unsigned flags = READ_ATTR_MACRO_OK;
> +	const char *attributes_file_path;
> +	struct repository *repo;
>   
>   	if (*stack)
>   		return;
> @@ -927,8 +921,13 @@ static void bootstrap_attr_stack(struct index_state *istate,
>   	}
>   
>   	/* home directory */
> -	if (git_attr_global_file()) {
> -		e = read_attr_from_file(git_attr_global_file(), flags);
> +	if (istate && istate->repo)
> +		repo = istate->repo;
> +	else
> +		repo = the_repository;
> +	attributes_file_path = repo_settings_get_attributesfile_path(repo);
> +	if (attributes_file_path) {
> +		e = read_attr_from_file(attributes_file_path, flags);
>   		push_stack(stack, e, NULL, 0);
>   	}
>   
> diff --git a/attr.h b/attr.h
> index a04a521092..956ce6ba62 100644
> --- a/attr.h
> +++ b/attr.h
> @@ -232,9 +232,6 @@ void attr_start(void);
>   /* Return the system gitattributes file. */
>   const char *git_attr_system_file(void);
>   
> -/* Return the global gitattributes file, if any. */
> -const char *git_attr_global_file(void);
> -
>   /* Return whether the system gitattributes file is enabled and should be used. */
>   int git_attr_system_is_enabled(void);
>   
> diff --git a/builtin/var.c b/builtin/var.c
> index cc3a43cde2..fd577f2930 100644
> --- a/builtin/var.c
> +++ b/builtin/var.c
> @@ -72,7 +72,7 @@ static char *git_attr_val_system(int ident_flag UNUSED)
>   
>   static char *git_attr_val_global(int ident_flag UNUSED)
>   {
> -	char *file = xstrdup_or_null(git_attr_global_file());
> +	char *file = xstrdup_or_null(repo_settings_get_attributesfile_path(the_repository));
>   	if (file) {
>   		normalize_path_copy(file, file);
>   		return file;
> diff --git a/environment.c b/environment.c
> index a770b5921d..ed7d8f42d9 100644
> --- a/environment.c
> +++ b/environment.c
> @@ -53,7 +53,6 @@ char *git_commit_encoding;
>   char *git_log_output_encoding;
>   char *apply_default_whitespace;
>   char *apply_default_ignorewhitespace;
> -char *git_attributes_file;
>   int zlib_compression_level = Z_BEST_SPEED;
>   int pack_compression_level = Z_DEFAULT_COMPRESSION;
>   int fsync_object_files = -1;
> @@ -363,11 +362,6 @@ static int git_default_core_config(const char *var, const char *value,
>   		return 0;
>   	}
>   
> -	if (!strcmp(var, "core.attributesfile")) {
> -		FREE_AND_NULL(git_attributes_file);
> -		return git_config_pathname(&git_attributes_file, var, value);
> -	}
> -
>   	if (!strcmp(var, "core.bare")) {
>   		is_bare_repository_cfg = git_config_bool(var, value);
>   		return 0;
> diff --git a/environment.h b/environment.h
> index 51898c99cd..3512a7072e 100644
> --- a/environment.h
> +++ b/environment.h
> @@ -152,7 +152,6 @@ extern int assume_unchanged;
>   extern int warn_on_object_refname_ambiguity;
>   extern char *apply_default_whitespace;
>   extern char *apply_default_ignorewhitespace;
> -extern char *git_attributes_file;
>   extern int zlib_compression_level;
>   extern int pack_compression_level;
>   extern unsigned long pack_size_limit_cfg;
> diff --git a/repo-settings.c b/repo-settings.c
> index 195c24e9c0..cc53a3cd3b 100644
> --- a/repo-settings.c
> +++ b/repo-settings.c
> @@ -5,6 +5,7 @@
>   #include "midx.h"
>   #include "pack-objects.h"
>   #include "setup.h"
> +#include "path.h"
>   
>   static void repo_cfg_bool(struct repository *r, const char *key, int *dest,
>   			  int def)
> @@ -158,6 +159,7 @@ void repo_settings_clear(struct repository *r)
>   	struct repo_settings empty = REPO_SETTINGS_INIT;
>   	FREE_AND_NULL(r->settings.fsmonitor);
>   	FREE_AND_NULL(r->settings.hooks_path);
> +	FREE_AND_NULL(r->settings.attributes_file_path);
>   	r->settings = empty;
>   }
>   
> @@ -230,3 +232,11 @@ void repo_settings_reset_shared_repository(struct repository *repo)
>   {
>   	repo->settings.shared_repository_initialized = 0;
>   }
> +const char *repo_settings_get_attributesfile_path(struct repository *repo)
> +{
> +	if (!repo->settings.attributes_file_path) {
> +		if (repo_config_get_pathname(repo, "core.attributesfile", &repo->settings.attributes_file_path))
> +			repo->settings.attributes_file_path = xdg_config_home("attributes");
> +	}
> +	return repo->settings.attributes_file_path;
> +}
> diff --git a/repo-settings.h b/repo-settings.h
> index d477885561..1209e1db83 100644
> --- a/repo-settings.h
> +++ b/repo-settings.h
> @@ -68,6 +68,7 @@ struct repo_settings {
>   	unsigned long big_file_threshold;
>   
>   	char *hooks_path;
> +	char *attributes_file_path;
>   };
>   #define REPO_SETTINGS_INIT { \
>   	.shared_repository = -1, \
> @@ -99,4 +100,11 @@ int repo_settings_get_shared_repository(struct repository *repo);
>   void repo_settings_set_shared_repository(struct repository *repo, int value);
>   void repo_settings_reset_shared_repository(struct repository *repo);
>   
> +/*
> + * Read the value for "core.attributesfile".
> + * Defaults to xdg_config_home("attributes") if the core.attributesfile
> + * which is set via repo config isn't available.
> + */
> +const char *repo_settings_get_attributesfile_path(struct repository *repo);
> +
>   #endif /* REPO_SETTINGS_H */
Phillip Wood· Jan 5, 2026, 15:00 UTC · re: Phillip Wood · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On 05/01/2026 14:23, Phillip Wood wrote:
Show 5 quoted lines
> 
> It is quite common that moving from parsing config settings eagerly by 
> calling repo_config() at startup to parsing them lazily via 'stuct 
> repo_settings' causes regressions like this. We really should find a way 
> to address that before moving more settings into 'struct repo_settings'

See https://lore.kernel.org/git/d61c966b-61ae-4ba9-b983-c8dab6e2c292@gmail.com for some discussion about a possible solution.

Thanks
Phillip
Junio C Hamano· Jan 5, 2026, 22:28 UTC · re: Phillip Wood · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

Phillip Wood <phillip.wood123@gmail.com> writes:
Show 10 quoted lines
> On 05/01/2026 14:23, Phillip Wood wrote:
>> 
>> It is quite common that moving from parsing config settings eagerly by 
>> calling repo_config() at startup to parsing them lazily via 'stuct 
>> repo_settings' causes regressions like this. We really should find a way 
>> to address that before moving more settings into 'struct repo_settings'
>
> See 
> https://lore.kernel.org/git/d61c966b-61ae-4ba9-b983-c8dab6e2c292@gmail.com 
> for some discussion about a possible solution.

Nice, but I suspect it would be an improvement already without passing repository instance via git_default_config() and instead have the code use the_repository; it is even possible not to have any repository when the callchain executes.

Bello Olamide· Jan 6, 2026, 09:33 UTC · re: Junio C Hamano · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On Mon, 5 Jan 2026 at 23:28, Junio C Hamano <gitster@pobox.com> wrote:
Show 18 quoted lines
>
> Phillip Wood <phillip.wood123@gmail.com> writes:
>
> > On 05/01/2026 14:23, Phillip Wood wrote:
> >>
> >> It is quite common that moving from parsing config settings eagerly by
> >> calling repo_config() at startup to parsing them lazily via 'stuct
> >> repo_settings' causes regressions like this. We really should find a way
> >> to address that before moving more settings into 'struct repo_settings'
> >
> > See
> > https://lore.kernel.org/git/d61c966b-61ae-4ba9-b983-c8dab6e2c292@gmail.com
> > for some discussion about a possible solution.
>
> Nice, but I suspect it would be an improvement already without
> passing repository instance via git_default_config() and instead
> have the code use the_repository; it is even possible not to have
> any repository when the callchain executes.

Thank you Junio. Okay, should I move the variable into the repo struct or repo-settings struct as the case may be, then initialize it in git_default_config()?

For example
`
         if (!strcmp(var, "core.attributesfile")) {
              FREE_AND_NULL(the_repository->settings.git_attributes_file);
              return
git_config_pathname(&the_repository->settings.git_attributes_file,
var, value);
            }
`
Does this sound reasonable?

Also the `git_default_core_config()` is used to initialize the variables to store the settings in the "core" section of the config file. So how about other functions like `git _default_branch_config()`, `git _default-sparse_config()`. Do I use the same approach above?

Thanks
Bello Olamide· Jan 6, 2026, 13:44 UTC · re: Bello Olamide · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On Tue, 6 Jan 2026 at 10:33, Bello Olamide <belkid98@gmail.com> wrote:
Show 20 quoted lines
>
> On Mon, 5 Jan 2026 at 23:28, Junio C Hamano <gitster@pobox.com> wrote:
> >
> > Phillip Wood <phillip.wood123@gmail.com> writes:
> >
> > > On 05/01/2026 14:23, Phillip Wood wrote:
> > >>
> > >> It is quite common that moving from parsing config settings eagerly by
> > >> calling repo_config() at startup to parsing them lazily via 'stuct
> > >> repo_settings' causes regressions like this. We really should find a way
> > >> to address that before moving more settings into 'struct repo_settings'
> > >
> > > See
> > > https://lore.kernel.org/git/d61c966b-61ae-4ba9-b983-c8dab6e2c292@gmail.com
> > > for some discussion about a possible solution.
> >
> > Nice, but I suspect it would be an improvement already without
> > passing repository instance via git_default_config() and instead
> > have the code use the_repository; it is even possible not to have
> > any repository when the callchain executes.

But won't this be a temporary solution since the goal is to prevent the use of `the_repository`?

Phillip Wood· Jan 7, 2026, 10:26 UTC · re: Bello Olamide · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On 06/01/2026 13:44, Bello Olamide wrote:
Show 24 quoted lines
> On Tue, 6 Jan 2026 at 10:33, Bello Olamide <belkid98@gmail.com> wrote:
>>
>> On Mon, 5 Jan 2026 at 23:28, Junio C Hamano <gitster@pobox.com> wrote:
>>>
>>> Phillip Wood <phillip.wood123@gmail.com> writes:
>>>
>>>> On 05/01/2026 14:23, Phillip Wood wrote:
>>>>>
>>>>> It is quite common that moving from parsing config settings eagerly by
>>>>> calling repo_config() at startup to parsing them lazily via 'stuct
>>>>> repo_settings' causes regressions like this. We really should find a way
>>>>> to address that before moving more settings into 'struct repo_settings'
>>>>
>>>> See
>>>> https://lore.kernel.org/git/d61c966b-61ae-4ba9-b983-c8dab6e2c292@gmail.com
>>>> for some discussion about a possible solution.
>>>
>>> Nice, but I suspect it would be an improvement already without
>>> passing repository instance via git_default_config() and instead
>>> have the code use the_repository; it is even possible not to have
>>> any repository when the callchain executes.
> 
> But won't this be a temporary solution since the goal is to prevent the use of
> `the_repository`?

Yes but it would be a good start as passing a repository down to git_default_config() will be quite invasive. It would certainly be better if we can find a solution that uses the repository passed to command when it is non-NULL. Unfortunately commands like "git diff --no-index" are passed a NULL repository but we have chosen to store our config in a `struct repository` and so we need some kind of fake repository for those commands. If we stored our config in a separate struct we wouldn't need to fake a repository but then we'd have to pass the config round separately to the repository which is a pain. Perhaps git_default_config() could use `the_repository` when it's given a NULL pointer for the callback data.

Thanks
Phillip
Phillip Wood· Jan 7, 2026, 14:18 UTC · re: Phillip Wood · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On 07/01/2026 10:26, Phillip Wood wrote:
Show 8 quoted lines
> On 06/01/2026 13:44, Bello Olamide wrote:
>>
>> But won't this be a temporary solution since the goal is to prevent 
>> the use of
>> `the_repository`?
> 
> Yes but it would be a good start as passing a repository down to 
> git_default_config() will be quite invasive.
To expand on this the first steps could be
   (i) create a new struct to hold the config settings from
       git_default_config()
  (ii) add that struct as a member of `struct repository`
(iii) one-by-one, for each setting parsed by git_default_config() add a
       new member to the config struct, store the parsed value in
       `the_repository` and adjust any code that uses the variable.

Then later we can tackle the intrusive change to pass a `struct repository` down to git_default_config() and store the settings in that rather than `the_repository`. If we add a local variable to git_default_config() in step (iii) above then getting it to use the repository passed down the call chain will simply be a matter of doing something like

- struct repository *r = the_repository; + struct repository *r = cb ? cb : the_repository;

Thanks
Phillip
Bello Olamide· Jan 7, 2026, 15:33 UTC · re: Phillip Wood · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On Wed, 7 Jan 2026 at 15:19, Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 33 quoted lines
>
> On 07/01/2026 10:26, Phillip Wood wrote:
> > On 06/01/2026 13:44, Bello Olamide wrote:
> >>
> >> But won't this be a temporary solution since the goal is to prevent
> >> the use of
> >> `the_repository`?
> >
> > Yes but it would be a good start as passing a repository down to
> > git_default_config() will be quite invasive.
>
> To expand on this the first steps could be
>    (i) create a new struct to hold the config settings from
>        git_default_config()
>   (ii) add that struct as a member of `struct repository`
> (iii) one-by-one, for each setting parsed by git_default_config() add a
>        new member to the config struct, store the parsed value in
>        `the_repository` and adjust any code that uses the variable.
>
> Then later we can tackle the intrusive change to pass a `struct
> repository` down to git_default_config() and store the settings in that
> rather than `the_repository`. If we add a local variable to
> git_default_config() in step (iii) above then getting it to use the
> repository passed down the call chain will simply be a matter of doing
> something like
>
> -       struct repository *r = the_repository;
> +       struct repository *r = cb ? cb : the_repository;
>
> Thanks
>
> Phillip
>

Thanks for the detailed proposal, which makes a lot of sense. I’ll align my changes with this staged approach.

Bello
Bello Olamide· Jan 6, 2026, 08:09 UTC · re: Phillip Wood · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On Mon, 5 Jan 2026 at 16:00, Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 11 quoted lines
>
> On 05/01/2026 14:23, Phillip Wood wrote:
> >
> > It is quite common that moving from parsing config settings eagerly by
> > calling repo_config() at startup to parsing them lazily via 'stuct
> > repo_settings' causes regressions like this. We really should find a way
> > to address that before moving more settings into 'struct repo_settings'
>
> See
> https://lore.kernel.org/git/d61c966b-61ae-4ba9-b983-c8dab6e2c292@gmail.com
> for some discussion about a possible solution.
Yes, thank you.
Junio C Hamano· Jan 5, 2026, 22:24 UTC · re: Phillip Wood · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

Phillip Wood <phillip.wood123@gmail.com> writes:
> If I run 'git -c core.attributesFile=~does-not-exist rebase -i' with git 
> built from master it fails immediately with "fatal: failed to expand 
> user dir in: '~does-not-exist'".

Hmph, if you call any behaviour change a "regression", this may certainly count as one, but I do not necessarily think the above is a good behaviour.

Think about a use case where attributes are not used at all, e.g., "git -c core.attributesFile=~does-not-matter cat-file -t HEAD"; would it make sense to barf when your configuration file has an invalid definition for what you are *not* using? So if the change makes it stop barfing, it can even be argued that this is an improvement.

> It is quite common that moving from parsing config settings eagerly by 
> calling repo_config() at startup to parsing them lazily via 'stuct 
> repo_settings' causes regressions like this. We really should find a way 
> to address that before moving more settings into 'struct repo_settings'

Very true. If we know the set of things we parse early and have a way to say "this command only X, Y, and Z matters (but not W)", then the above cat-file example can omit the attributesFile from the "we care" set.

I think overusing repo_settings is a disease. Moving a singleton global to per repository (by adding to struct repository) is one thing and it is very welcome. But changing the way configuration variables are parsed (e.g., what used to be parsed by only those who care about is now parsed by everybody, or vice versa) needs to be handled carefully.

Phillip Wood· Jan 7, 2026, 10:17 UTC · re: Junio C Hamano · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On 05/01/2026 22:24, Junio C Hamano wrote:
Show 9 quoted lines
> Phillip Wood <phillip.wood123@gmail.com> writes:
> 
>> If I run 'git -c core.attributesFile=~does-not-exist rebase -i' with git
>> built from master it fails immediately with "fatal: failed to expand
>> user dir in: '~does-not-exist'".
> 
> Hmph, if you call any behaviour change a "regression", this may
> certainly count as one, but I do not necessarily think the above is
> a good behaviour.

One can argue that it depends on the command, but a as far as "git rebase" is concerned this change in behavior is a regression. It is not just rebase that is affected, for example "git merge" in a partial clone now downloads all the blobs it wants before erroring out which isn't terrible but it is hard to argue that's an improvement. I suspect "git cherry-pick" and "git revert" are also negatively impacted by this change.

Show 6 quoted lines
> Think about a use case where attributes are not used at all, e.g.,
> "git -c core.attributesFile=~does-not-matter cat-file -t HEAD";
> would it make sense to barf when your configuration file has an
> invalid definition for what you are *not* using?  So if the change
> makes it stop barfing, it can even be argued that this is an
> improvement.

For some commands, but the fact that other commands now die when they're not expecting to and so end up in a strange state is a regression. Given how widespread the use of attributes is it would be hard to audit all the affected code paths and adjust them to the new behavior.

Show 9 quoted lines
>> It is quite common that moving from parsing config settings eagerly by
>> calling repo_config() at startup to parsing them lazily via 'stuct
>> repo_settings' causes regressions like this. We really should find a way
>> to address that before moving more settings into 'struct repo_settings'
> 
> Very true.  If we know the set of things we parse early and have a
> way to say "this command only X, Y, and Z matters (but not W)", then
> the above cat-file example can omit the attributesFile from the "we
> care" set.

That would be nice, but it would be painful to implement for commands like cat-file which sometimes need to read core.attributesFile and sometimes don't depending on which options they're passed as we typically read the config before parsing the command line options. We'd also need to be careful to update the list when adding new features which depended on config variables that were not previously used by that command.

Show 6 quoted lines
> I think overusing repo_settings is a disease.  Moving a singleton
> global to per repository (by adding to struct repository) is one
> thing and it is very welcome.  But changing the way configuration
> variables are parsed (e.g., what used to be parsed by only those who
> care about is now parsed by everybody, or vice versa) needs to be
> handled carefully.
Indeed
Thanks
Phillip
Bello Olamide· Jan 6, 2026, 08:08 UTC · re: Phillip Wood · lore

Re: [Outreachy PATCH v2] environment: move "core.attributesFile" into repo-setting

On Mon, 5 Jan 2026 at 15:23, Phillip Wood <phillip.wood123@gmail.com> wrote:
Show 26 quoted lines
>
> Hi Olamide
>
> On 02/01/2026 16:32, Olamide Caleb Bello wrote:
> > When handling multiple repositories within the same process, relying on
> > global state for accessing the "core.attributesFile" configuration can
> > lead to incorrect values being used. It also makes it harder to isolate
> > repositories and hinders the libification of git.
> > The functions `bootstrap_attr_stack()` and `git_attr_val_system()`
> > retrieve "core.attributesFile" via `git_attr_global_file()`
> > which reads from global state `git_attributes_file`.
> >
> > Move the "core.attributesFile" configuration into the
> > `struct repo_settings` instead of relying on the global state.
>
> This changes when the config setting gets parsed which unfortunately
> regresses the user experience when the setting is invalid.
>
> If I run 'git -c core.attributesFile=~does-not-exist rebase -i' with git
> built from master it fails immediately with "fatal: failed to expand
> user dir in: '~does-not-exist'". With this patch applied it prompts me
> to edit the todo list and then fails when it tries to checkout the
> commit we're rebasing onto. Because "git rebase" expects reset_head() to
> return an error rather die if the checkout fails it is left in a strange
> state where only practical course of action for the user is to run "git
> rebase --abort".
Yes I tried this and I experienced the same behaviour.
Show 6 quoted lines
>
> It is quite common that moving from parsing config settings eagerly by
> calling repo_config() at startup to parsing them lazily via 'stuct
> repo_settings' causes regressions like this. We really should find a way
> to address that before moving more settings into 'struct repo_settings'
>

Yes, I came across an initial discussion about `prepare_repo_settings()` and the issues about the appropriate place to call it but it seemed there was no resolution then.

← back to recent threads