{"thread":{"id":"65873","subject":"[PATCH v2 0/2] environment: move excludes_file into repo_config_values","startedAt":"2026-06-26T07:50:50Z","lastAt":"2026-07-14T16:24:56Z","messageCount":114,"participants":["Tian Yuchen","Junio C Hamano","SZEDER Gábor","Christian Couder","Pablo Sabater"],"isPatch":true,"patchVersion":2,"patchTotal":2},"messages":[{"id":"546448","messageId":"20260626075037.532164-1-cat@malon.dev","threadId":"65873","inReplyTo":null,"subject":"[PATCH v2 0/2] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-26T07:50:35Z","receivedAt":"2026-06-26T07:50:50Z","isPatch":true,"body":"This series continues the libification effort by migrating the global\nstring variable 'excludes_file' into 'struct repo_config_values'. Since\nthis is a dynamically allocated variable, the migration requires proper\nheap memory management.\n\nThe series is structured in two commits:\n\n - Abstract the XDG fallback lazy-loading logic out of dir.c into a proper\ngetter.\n\n - Move the variables into the struct and introduce 'repo_config_values_clear()'.\n\nNote on Submodules: A temporary shield 'if (repo != the_repository)' is\nincluded in both the getter and the clear function. This prevents\nuninitialized submodules from triggering the BUG() assertion.\n(Inspiration: [1])\n\nChanges since V1:\n\n - fix a typo in the second commit.\n\n - initialize excludes_file to NULL in repo_config_values_init().\n\n - call FREE_AND_NULL() to free attributes_file as well in\n repo_config_values_clear().\n\nThanks!\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n\n[1] https://lore.kernel.org/git/c95a7730-7b14-4be0-a4e4-861b2f5430ea@gmail.com/\n\nTian Yuchen (2):\n  dir: encapsulate excludes_file lazy-load\n  environment: move excludes_file into repo_config_values\n\n dir.c         |  4 ++--\n environment.c | 32 +++++++++++++++++++++++++++++---\n environment.h | 13 ++++++++++++-\n repository.c  |  1 +\n 4 files changed, 44 insertions(+), 6 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"546449","messageId":"20260626075037.532164-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260626075037.532164-1-cat@malon.dev","subject":"[PATCH v2 1/2] dir: encapsulate excludes_file lazy-load","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-26T07:50:36Z","receivedAt":"2026-06-26T07:50:53Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file, 'core.excludesfile'. If this variable is NULL,\nsetup_standard_excludes() in dir.c forcefully evaluates and assigns\nthe XDG default path to it.\n\nIntroduce repo_excludes_file() as a getter to encapsulate this\nlazy-loading logic. This prepares the variable to be safely moved\ninto 'struct repo_config_values' in the subsequent commit.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         | 4 ++--\n environment.c | 7 +++++++\n environment.h | 5 +++++\n 3 files changed, 14 insertions(+), 2 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..8efcaeafa6 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -134,6 +134,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tif (!excludes_file)\n+\t\texcludes_file = xdg_config_home(\"ignore\");\n+\treturn excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..52d531e4ea 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -133,6 +133,11 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+/*\n+ * TODO: This still relies on the global state.\n+ */\n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n-- \n2.43.0\n\n"},{"id":"546450","messageId":"20260626075037.532164-3-cat@malon.dev","threadId":"65873","inReplyTo":"20260626075037.532164-1-cat@malon.dev","subject":"[PATCH v2 2/2] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-26T07:50:37Z","receivedAt":"2026-06-26T07:50:58Z","isPatch":true,"body":"Continue the libification effort by moving the 'excludes_file' global\nvariable into 'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string (char *), it\nrequires proper memory management. Introduce repo_config_values_clear()\nto safely free the heap memory when repository instance is destroyed.\n\nNote:\n\n - 'if (repo != the_repository)' fallback logic is temporarily added\nin both the getter and the clear function. This prevents calling\nrepo_config_values() on uninitialized submodules, which triggers BUG().\n\n - 'attribute_file' is another string variable that was migrated\n earlier. Its FREE_AND_NULL() call is also added to\n repo_config_values_clear().\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 31 +++++++++++++++++++++++++------\n environment.h | 14 ++++++++++----\n repository.c  |  1 +\n 3 files changed, 36 insertions(+), 10 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 8efcaeafa6..b8dfa3e213 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -136,9 +135,13 @@ int is_bare_repository(void)\n \n const char *repo_excludes_file(struct repository *repo)\n {\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n-\treturn excludes_file;\n+\tif (!repo || !repo->initialized || repo != the_repository)\n+\t\treturn NULL;\n+\n+\tif (!repo_config_values(repo)->excludes_file)\n+\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn repo_config_values(repo)->excludes_file;\n }\n \n int have_git_dir(void)\n@@ -468,8 +471,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -722,6 +725,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -733,3 +737,18 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg;\n+\n+\tif (repo != the_repository)\n+\t\treturn;\n+\n+\tcfg = repo_config_values(repo);\n+\tif (!cfg)\n+\t\treturn;\n+\n+\tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 52d531e4ea..2e8352de7f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,13 +134,19 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n-/*\n- * TODO: This still relies on the global state.\n- */\n const char *repo_excludes_file(struct repository *repo);\n \n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repository *repo);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\n@@ -213,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b31f1b7852 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(repo);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"546482","messageId":"xmqqh5mp9uke.fsf@gitster.g","threadId":"65873","inReplyTo":"20260626075037.532164-1-cat@malon.dev","subject":"Re: [PATCH v2 0/2] environment: move excludes_file into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-26T15:42:57Z","receivedAt":"2026-06-26T15:42:59Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> This series continues the libification effort by migrating the global\n> string variable 'excludes_file' into 'struct repo_config_values'. Since\n> this is a dynamically allocated variable, the migration requires proper\n> heap memory management.\n\nThis appears here:\n\n  https://lore.kernel.org/git/20260626075037.532164-1-cat@malon.dev/\n\nand as you can see, there is no linkage back to the previous round.\n\nThe lack of In-Reply-To and References headers unfortunately delays my\nautomation in marking topics with newer iterations available to be\nreviewed when I come back to the keyboard, which happens overnight.\n"},{"id":"546483","messageId":"xmqqbjcx9ujm.fsf@gitster.g","threadId":"65873","inReplyTo":"20260626075037.532164-3-cat@malon.dev","subject":"Re: [PATCH v2 2/2] environment: move excludes_file into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-26T15:43:25Z","receivedAt":"2026-06-26T15:43:28Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> Continue the libification effort by moving the 'excludes_file' global\n> variable into 'struct repo_config_values'.\n>\n> Since 'excludes_file' is a dynamically allocated string (char *), it\n> requires proper memory management. Introduce repo_config_values_clear()\n> to safely free the heap memory when repository instance is destroyed.\n>\n> Note:\n>\n>  - 'if (repo != the_repository)' fallback logic is temporarily added\n> in both the getter and the clear function. This prevents calling\n> repo_config_values() on uninitialized submodules, which triggers BUG().\n>\n>  - 'attribute_file' is another string variable that was migrated\n>  earlier. Its FREE_AND_NULL() call is also added to\n>  repo_config_values_clear().\n\nOK.  I think the placement of the new member in repo_config_values\nin this round, moving to the spot next to existing attributes_file,\nmakes more sense than the previous round, too.\n\nLooking good.  Shall we mark it as ready for 'next' now?\n\n"},{"id":"546510","messageId":"xmqqjyrl6rpt.fsf@gitster.g","threadId":"65873","inReplyTo":"20260626075037.532164-3-cat@malon.dev","subject":"Re: [PATCH v2 2/2] environment: move excludes_file into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-26T19:12:46Z","receivedAt":"2026-06-26T19:12:48Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> Continue the libification effort by moving the 'excludes_file' global\n> variable into 'struct repo_config_values'.\n>\n> Since 'excludes_file' is a dynamically allocated string (char *), it\n> requires proper memory management. Introduce repo_config_values_clear()\n> to safely free the heap memory when repository instance is destroyed.\n>\n> Note:\n>\n>  - 'if (repo != the_repository)' fallback logic is temporarily added\n> in both the getter and the clear function. This prevents calling\n> repo_config_values() on uninitialized submodules, which triggers BUG().\n\nWould it be possible for the function to be called on the_repository\nbefore it gets initialized?\n\n> +void repo_config_values_clear(struct repository *repo)\n> +{\n> +\tstruct repo_config_values *cfg;\n\nWhat I am wondering is if this check\n\n> +\tif (repo != the_repository)\n> +\t\treturn;\n\nwants to be more like\n\n\tif (!repo->initialized)\n\t\treturn;\n\nor even\n\n\tif (!repo->initialized) {\n\t\tBUG(\"clearing uninitialised repo config\");\n\t\treturn;\n\t}\n\nOr perhaps not doing anything special there.\n\nFor that matter,\n\n> +\n> +\tcfg = repo_config_values(repo);\n> +\tif (!cfg)\n> +\t\treturn;\n\nWouldn't it be a bug to see NULL returned from the above function?\nWhy is it healthy to pretend as if nothing bad happened?\n\n> +\tFREE_AND_NULL(cfg->attributes_file);\n> +\tFREE_AND_NULL(cfg->excludes_file);\n> +}\n\n\nWhat do we want to happen when somebody does want to access (not\n_clear(), but repo_config_values() itself) repo_config_values() in\ntoday's code?  Don't we want to catch such a code as buggy?  Isn't\nit the reason why repository.c:repo_config_values() check these\nconditions and calls BUG() in the first place?  And if that is the\ncase, I find that a caller that \"works around\" by pretending nothing\nbad happened and not calling repo_config_values(), like the above\ncode does, highly questionable, as it smells like sweeping problems\nthat you designed other parts of the code to detect with BUG() under\nthe rug.\n\nIn the longer run, we would want to have separate settings, which\nused to be global variables but now are stored in per repository\nconfig, available and usable in the context of each repository that\nthey are configured within.  If a caller wants to clear per repo\nconfig for a repository instance by calling this function, this\nfunction is in no place to tweak the intention of the caller by\nshort-circuiting the request and pretending it did what it was asked\nto do.  In other words, the rest of the code not quite prepared to\ndeal with these global variables that turned into per repository\nconfiguration values is *not* a problem this function should\naddress.  Let it be noticed by repo_config_values() function to\ncatch offending callers for now, and once the codebase becomes ready\nto use one repo_config_values per repository, this function does not\nhave to change.\n"},{"id":"546514","messageId":"aj7rtj9NsejqN357@szeder.dev","threadId":"65873","inReplyTo":"20260626075037.532164-2-cat@malon.dev","subject":"Re: [PATCH v2 1/2] dir: encapsulate excludes_file lazy-load","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-06-26T21:14:30Z","receivedAt":"2026-06-26T21:14:44Z","isPatch":true,"body":"On Fri, Jun 26, 2026 at 03:50:36PM +0800, Tian Yuchen wrote:\n> diff --git a/environment.c b/environment.c\n> index ba2c60103f..8efcaeafa6 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -134,6 +134,13 @@ int is_bare_repository(void)\n>  \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>  }\n>  \n> +const char *repo_excludes_file(struct repository *repo)\n> +{\n> +\tif (!excludes_file)\n> +\t\texcludes_file = xdg_config_home(\"ignore\");\n> +\treturn excludes_file;\n> +}\n\nThis function has a 'repo' parameter, which is not used in the\nfunction at all.  This causes build failure when trying to build this\ncommit using DEVELOPER=1:\n\n  environment.c: In function ‘repo_excludes_file’:\n  environment.c:137:51: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n    137 | const char *repo_excludes_file(struct repository *repo)\n        |                                ~~~~~~~~~~~~~~~~~~~^~~~\n  cc1: all warnings being treated as errors\n  make: *** [Makefile:2922: environment.o] Error 1\n\nPlease make sure that all commits can be built with 'make\nDEVELOPER=1'.\n\n"},{"id":"546517","messageId":"xmqqldc1563l.fsf@gitster.g","threadId":"65873","inReplyTo":"aj7rtj9NsejqN357@szeder.dev","subject":"Re: [PATCH v2 1/2] dir: encapsulate excludes_file lazy-load","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-26T21:45:02Z","receivedAt":"2026-06-26T21:45:06Z","isPatch":true,"body":"SZEDER Gábor <szeder.dev@gmail.com> writes:\n\n> On Fri, Jun 26, 2026 at 03:50:36PM +0800, Tian Yuchen wrote:\n>> diff --git a/environment.c b/environment.c\n>> index ba2c60103f..8efcaeafa6 100644\n>> --- a/environment.c\n>> +++ b/environment.c\n>> @@ -134,6 +134,13 @@ int is_bare_repository(void)\n>>  \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>>  }\n>>  \n>> +const char *repo_excludes_file(struct repository *repo)\n>> +{\n>> +\tif (!excludes_file)\n>> +\t\texcludes_file = xdg_config_home(\"ignore\");\n>> +\treturn excludes_file;\n>> +}\n>\n> This function has a 'repo' parameter, which is not used in the\n> function at all.  This causes build failure when trying to build this\n> commit using DEVELOPER=1:\n>\n>   environment.c: In function ‘repo_excludes_file’:\n>   environment.c:137:51: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n>     137 | const char *repo_excludes_file(struct repository *repo)\n>         |                                ~~~~~~~~~~~~~~~~~~~^~~~\n>   cc1: all warnings being treated as errors\n>   make: *** [Makefile:2922: environment.o] Error 1\n>\n> Please make sure that all commits can be built with 'make\n> DEVELOPER=1'.\n\nGood point.  In this case, we can start with UNUSED in step 1/2 and\nthen drop the UNUSED in the second step.  I wonder how harder to read\nit would become if these two patches are squashed together...\n\nThanks.\n"},{"id":"546535","messageId":"73c52aeb-238b-4028-85c1-65ba05aa9b95@malon.dev","threadId":"65873","inReplyTo":"xmqqh5mp9uke.fsf@gitster.g","subject":"Re: [PATCH v2 0/2] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-27T13:56:30Z","receivedAt":"2026-06-27T13:56:46Z","isPatch":true,"body":"On 6/26/26 23:42, Junio C Hamano wrote:\n\nSorry, there was a problem with my Thunderbird client... so the my \nemails didn't send without me noticing...\n\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> This series continues the libification effort by migrating the global\n>> string variable 'excludes_file' into 'struct repo_config_values'. Since\n>> this is a dynamically allocated variable, the migration requires proper\n>> heap memory management.\n> \n> This appears here:\n> \n>    https://lore.kernel.org/git/20260626075037.532164-1-cat@malon.dev/\n> \n> and as you can see, there is no linkage back to the previous round.\n> \n> The lack of In-Reply-To and References headers unfortunately delays my\n> automation in marking topics with newer iterations available to be\n> reviewed when I come back to the keyboard, which happens overnight.\n\nRegarding this, I forgot to bring --in-reply-to. I'll remember next time.\n\nThanks, yuchen\n"},{"id":"546536","messageId":"5abe7da6-5fac-4f4f-8ad9-93811c8c82e5@malon.dev","threadId":"65873","inReplyTo":"xmqqjyrl6rpt.fsf@gitster.g","subject":"Re: [PATCH v2 2/2] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-27T14:10:16Z","receivedAt":"2026-06-27T14:10:25Z","isPatch":true,"body":"On 6/27/26 03:12, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> Continue the libification effort by moving the 'excludes_file' global\n>> variable into 'struct repo_config_values'.\n>>\n>> Since 'excludes_file' is a dynamically allocated string (char *), it\n>> requires proper memory management. Introduce repo_config_values_clear()\n>> to safely free the heap memory when repository instance is destroyed.\n>>\n>> Note:\n>>\n>>   - 'if (repo != the_repository)' fallback logic is temporarily added\n>> in both the getter and the clear function. This prevents calling\n>> repo_config_values() on uninitialized submodules, which triggers BUG().\n> \n> Would it be possible for the function to be called on the_repository\n> before it gets initialized?\n> \n>> +void repo_config_values_clear(struct repository *repo)\n>> +{\n>> +\tstruct repo_config_values *cfg;\n> \n> What I am wondering is if this check\n> \n>> +\tif (repo != the_repository)\n>> +\t\treturn;\n> \n> wants to be more like\n> \n> \tif (!repo->initialized)\n> \t\treturn;\n> \n> or even\n> \n> \tif (!repo->initialized) {\n> \t\tBUG(\"clearing uninitialised repo config\");\n> \t\treturn;\n> \t}\n> \n> Or perhaps not doing anything special there.\n> \n> For that matter,\n> \n>> +\n>> +\tcfg = repo_config_values(repo);\n>> +\tif (!cfg)\n>> +\t\treturn;\n> \n> Wouldn't it be a bug to see NULL returned from the above function?\n> Why is it healthy to pretend as if nothing bad happened?\n> \n>> +\tFREE_AND_NULL(cfg->attributes_file);\n>> +\tFREE_AND_NULL(cfg->excludes_file);\n>> +}\n> \n> \n> What do we want to happen when somebody does want to access (not\n> _clear(), but repo_config_values() itself) repo_config_values() in\n> today's code?  Don't we want to catch such a code as buggy?  Isn't\n> it the reason why repository.c:repo_config_values() check these\n> conditions and calls BUG() in the first place?  And if that is the\n> case, I find that a caller that \"works around\" by pretending nothing\n> bad happened and not calling repo_config_values(), like the above\n> code does, highly questionable, as it smells like sweeping problems\n> that you designed other parts of the code to detect with BUG() under\n> the rug.\n> \n> In the longer run, we would want to have separate settings, which\n> used to be global variables but now are stored in per repository\n> config, available and usable in the context of each repository that\n> they are configured within.  If a caller wants to clear per repo\n> config for a repository instance by calling this function, this\n> function is in no place to tweak the intention of the caller by\n> short-circuiting the request and pretending it did what it was asked\n> to do.  In other words, the rest of the code not quite prepared to\n> deal with these global variables that turned into per repository\n> configuration values is *not* a problem this function should\n> address.  Let it be noticed by repo_config_values() function to\n> catch offending callers for now, and once the codebase becomes ready\n> to use one repo_config_values per repository, this function does not\n> have to change.\n\nYes, indeed. I initially thought these were two solutions leading to the \nsame goal, but now that you mention it, I think your point is more valid.\n\nFirst, 'repo_config_values_clear()' shouldn't know whether \"only \n'the_repository' is currently supported\" (even if this logic is \nnecessary, this function is clearly not the optimal place), so we \nshouldn't use 'repo != the_repository' to determine this.\n\nSecond, what's wrong with '!cfg' is that what it's supposed to avoid \ndeviates from what it tries to avoid. 'repo_config_values(repo)' \nshouldn't validly return NULL at all. I admit this code is nonsensical.\n\nUsing 'repo->initialized' to determine this is consistent with our \nprevious approach. I'll try it. Thanks for the review!\n\nRegards, yuchen\n"},{"id":"546537","messageId":"8a559dcc-43e9-46fa-93a1-08e360a3490a@malon.dev","threadId":"65873","inReplyTo":"xmqqldc1563l.fsf@gitster.g","subject":"Re: [PATCH v2 1/2] dir: encapsulate excludes_file lazy-load","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-27T14:13:16Z","receivedAt":"2026-06-27T14:13:27Z","isPatch":true,"body":"On 6/27/26 05:45, Junio C Hamano wrote:\n> SZEDER Gábor <szeder.dev@gmail.com> writes:\n> \n>> On Fri, Jun 26, 2026 at 03:50:36PM +0800, Tian Yuchen wrote:\n>>> diff --git a/environment.c b/environment.c\n>>> index ba2c60103f..8efcaeafa6 100644\n>>> --- a/environment.c\n>>> +++ b/environment.c\n>>> @@ -134,6 +134,13 @@ int is_bare_repository(void)\n>>>   \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>>>   }\n>>>   \n>>> +const char *repo_excludes_file(struct repository *repo)\n>>> +{\n>>> +\tif (!excludes_file)\n>>> +\t\texcludes_file = xdg_config_home(\"ignore\");\n>>> +\treturn excludes_file;\n>>> +}\n>>\n>> This function has a 'repo' parameter, which is not used in the\n>> function at all.  This causes build failure when trying to build this\n>> commit using DEVELOPER=1:\n>>\n>>    environment.c: In function ‘repo_excludes_file’:\n>>    environment.c:137:51: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n>>      137 | const char *repo_excludes_file(struct repository *repo)\n>>          |                                ~~~~~~~~~~~~~~~~~~~^~~~\n>>    cc1: all warnings being treated as errors\n>>    make: *** [Makefile:2922: environment.o] Error 1\n>>\n>> Please make sure that all commits can be built with 'make\n>> DEVELOPER=1'.\n> \n> Good point.  In this case, we can start with UNUSED in step 1/2 and\n> then drop the UNUSED in the second step.  I wonder how harder to read\n> it would become if these two patches are squashed together...\n\nThat makes sense. I think these two commits can be squadshed together... \nI hope so.\n\n> \n> Thanks.\n\nRegards, yuchen\n"},{"id":"546544","messageId":"20260627160813.1074201-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260626075037.532164-1-cat@malon.dev","subject":"[PATCH v4 0/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-27T16:08:12Z","receivedAt":"2026-06-27T16:08:27Z","isPatch":true,"body":"This patch continues the libification effort by migrating the global\nstring variable 'excludes_file' into 'struct repo_config_values'. Since\nthis is a dynamically allocated variable, the migration requires proper\nheap memory management.\n\nThis patch mainly does three things:\n\n - Abstract the XDG fallback lazy-loading logic out of dir.c into a proper\n getter.\n\n - Move the variables into the struct repo_config_values.\n\n - Introduce the memory destructor 'repo_config_values_clear()'.\n\n\nChanges since V2:\n\n - Squash together the previous two commits into one.\n\n - The 'repo->initialized' check is used in both the getter and destructor.\n This eliminates redundant checks and follows the fail-fast principle. This\n is consistent with the previous global variable removal patches [1][2][3].\n\nNote: Resending as v3 purely to fix a malformed In-Reply-To header in v2\nthat broke the email thread. No actual code changes since v2...\n\nThanks!\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n\n[1] https://lore.kernel.org/git/20260610093635.139719-1-cat@malon.dev/T/#m856253610936d052a798259bfc06d598561e53c4\n[2] https://lore.kernel.org/git/20260606143412.15443-1-cat@malon.dev/\n[3] https://lore.kernel.org/git/20260617154929.564498-2-cat@malon.dev/T/#m8843984a6175a1a4c7e00877085c77b0c72f5803\n\nTian Yuchen (1):\n  environment: move excludes_file into repo_config_values\n\n dir.c         |  4 ++--\n environment.c | 30 +++++++++++++++++++++++++++---\n environment.h | 13 ++++++++++++-\n repository.c  |  1 +\n 4 files changed, 42 insertions(+), 6 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"546545","messageId":"20260627160813.1074201-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260627160813.1074201-1-cat@malon.dev","subject":"[PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-27T16:08:13Z","receivedAt":"2026-06-27T16:08:30Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL, setup_standard_excludes()\nin dir.c forcefully evaluates and assigns the XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. Introduce repo_config_values_clear()\nand wire it up in repo_clear() to safely free this memory when a\nrepository instance is destroyed. Also clean up the heap-allocated\n'attributes_file' in this new destructor while we are at it.\n\nNote: 'if (!repo->initialized)' is added in both the getter and the\ndestructor. This ensures we safely return or bypass cleaning up\nuninitialized repositories without hardcoding a dependency on\n'the_repository'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 30 +++++++++++++++++++++++++++---\n environment.h | 13 ++++++++++++-\n repository.c  |  1 +\n 4 files changed, 42 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..2519c60918 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,17 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tif (!repo || !repo->initialized)\n+\t\treturn NULL;\n+\n+\tif (!repo_config_values(repo)->excludes_file)\n+\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn repo_config_values(repo)->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +471,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +725,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -726,3 +737,16 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg;\n+\n+\tif (repo->initialized)\n+\t\treturn;\n+\n+\tcfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..2e8352de7f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,8 +134,19 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repository *repo);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\n@@ -208,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b31f1b7852 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(repo);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"546546","messageId":"04d1a7d5-ef83-4728-b816-5cdf1cb4aa25@malon.dev","threadId":"65873","inReplyTo":"20260627160813.1074201-2-cat@malon.dev","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-27T16:10:49Z","receivedAt":"2026-06-27T16:11:01Z","isPatch":true,"body":"Hi all,\n\nApologies again for the duplicate...\n\nOn 6/28/26 00:08, Tian Yuchen wrote:\n\n> +const char *repo_excludes_file(struct repository *repo)\n> +{\n> +\tif (!repo || !repo->initialized)\n> +\t\treturn NULL;\n> +\n> +\tif (!repo_config_values(repo)->excludes_file)\n> +\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n> +\n> +\treturn repo_config_values(repo)->excludes_file;\n> +}\n\nOne more thing:\n\nI deliberately didn't write a comment for the getter because it will \nprobably be merged with comments from the previous several patches in \nsome form in the near future... I'm not sure if it would be more \nappropriate to write a separate patch to add the corresponding comments \nthen.\n\nRegards, yuchen\n"},{"id":"546564","messageId":"xmqqv7b34snt.fsf@gitster.g","threadId":"65873","inReplyTo":"04d1a7d5-ef83-4728-b816-5cdf1cb4aa25@malon.dev","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-27T20:47:34Z","receivedAt":"2026-06-27T20:47:37Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> Hi all,\n>\n> Apologies again for the duplicate...\n>\n> On 6/28/26 00:08, Tian Yuchen wrote:\n>\n>> +const char *repo_excludes_file(struct repository *repo)\n>> +{\n>> +\tif (!repo || !repo->initialized)\n>> +\t\treturn NULL;\n\nI might already have said this, but I am not sure why want to be as\nloose as this code.  It is not limited to this line, but I think we\nsaw plenty of other \"We know we must get an already initialized\nthing here, and the subsequent operation we perform on that thing\nwill cause us to die() later, so let's return silently and early\nto avoid hitting die()\" attempts to sweep problems under the rug.\n\nWouldn't we rather want to try to be more strict and say\n\n\tif (!repo || !repo->initialized)\n\t\tBUG(\"repo must be an initialied repository\");\n\nhere?  Aren't all the callers of this function supposed to be\ndealing with an already initialized repository?\n\n\n>> +\tif (!repo_config_values(repo)->excludes_file)\n>> +\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n>> +\n>> +\treturn repo_config_values(repo)->excludes_file;\n>> +}\n>\n> One more thing:\n>\n> I deliberately didn't write a comment for the getter because it will \n> probably be merged with comments from the previous several patches in \n> some form in the near future... I'm not sure if it would be more \n> appropriate to write a separate patch to add the corresponding comments \n> then.\n\nThat's very sensible.\n\n"},{"id":"546578","messageId":"eabb8169-2c13-4961-9b21-f44b1fa66f70@malon.dev","threadId":"65873","inReplyTo":"xmqqv7b34snt.fsf@gitster.g","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-28T03:19:55Z","receivedAt":"2026-06-28T03:20:06Z","isPatch":true,"body":"On 6/28/26 04:47, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> Hi all,\n>>\n>> Apologies again for the duplicate...\n>>\n>> On 6/28/26 00:08, Tian Yuchen wrote:\n>>\n>>> +const char *repo_excludes_file(struct repository *repo)\n>>> +{\n>>> +\tif (!repo || !repo->initialized)\n>>> +\t\treturn NULL;\n> \n> I might already have said this, but I am not sure why want to be as\n> loose as this code.  It is not limited to this line, but I think we\n> saw plenty of other \"We know we must get an already initialized\n> thing here, and the subsequent operation we perform on that thing\n> will cause us to die() later, so let's return silently and early\n> to avoid hitting die()\" attempts to sweep problems under the rug.\n> \n> Wouldn't we rather want to try to be more strict and say\n> \n> \tif (!repo || !repo->initialized)\n> \t\tBUG(\"repo must be an initialied repository\");\n> \n> here?  Aren't all the callers of this function supposed to be\n> dealing with an already initialized repository?\n> \n\nThat makes sense, but from my point of view...\n\n'repo_config_values()' already has a check for 'repo->initialized'. If \nwe're absolutely certain that the 'repo' is initialized, wouldn't it be \nbetter to simply remove all the checks inside the getter and leave the \njudgment to 'repo_config_values()'?\n\nThis also aligns to some extent with the previous flag getters: since an \nuninitialized 'repo' will trigger a BUG() in 'repo_config_values()', but \n\"reading and writing these flags when the 'repo' is uninitialized\" is \nsometimes a valid operation that's why we choose to intercept \n'repo->initialized' _before_ 'repo_config_values()' and fallback to the \nhard-coded values.\n\nThis gives the impression that _'repo_config_values()' is the function \nresponsible for checking_, and the way flags are handled is an exception \nto this approach, which I think is more consistent and self-explanatory.\n\nWhat do you think?\n\n\n> \n>>> +\tif (!repo_config_values(repo)->excludes_file)\n>>> +\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n>>> +\n>>> +\treturn repo_config_values(repo)->excludes_file;\n>>> +}\n>>\n>> One more thing:\n>>\n>> I deliberately didn't write a comment for the getter because it will\n>> probably be merged with comments from the previous several patches in\n>> some form in the near future... I'm not sure if it would be more\n>> appropriate to write a separate patch to add the corresponding comments\n>> then.\n> \n> That's very sensible.\n> \n\nThanks.\n\nregards, yuchen\n"},{"id":"546579","messageId":"ef7f76d9-52f4-45c0-af0e-b7f3372b652a@malon.dev","threadId":"65873","inReplyTo":"eabb8169-2c13-4961-9b21-f44b1fa66f70@malon.dev","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-28T03:38:00Z","receivedAt":"2026-06-28T03:38:07Z","isPatch":true,"body":"On 6/28/26 11:19, Tian Yuchen wrote:\n\n>  Let it be noticed by repo_config_values() function to\n> catch offending callers for now, and once the codebase becomes ready\n> to use one repo_config_values per repository, this function does not\n> have to change.\n\nAnd\n\n> Wouldn't we rather want to try to be more strict and say\n> \n> \tif (!repo || !repo->initialized)\n> \t\tBUG(\"repo must be an initialied repository\");\n> \n> here?  Aren't all the callers of this function supposed to be\n> dealing with an already initialized repository?\n\nIn my opinion, these two suggestions are not entirely consistent, and I \nthink we need to determine the most appropriate approach.\n\nRegards, yuchen\n"},{"id":"546594","messageId":"xmqqbjcv2h3j.fsf@gitster.g","threadId":"65873","inReplyTo":"eabb8169-2c13-4961-9b21-f44b1fa66f70@malon.dev","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-28T08:40:16Z","receivedAt":"2026-06-28T08:40:20Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n>> Wouldn't we rather want to try to be more strict and say\n>> \n>> \tif (!repo || !repo->initialized)\n>> \t\tBUG(\"repo must be an initialied repository\");\n>> \n>> here?  Aren't all the callers of this function supposed to be\n>> dealing with an already initialized repository?\n>\n> That makes sense, but from my point of view...\n>\n> 'repo_config_values()' already has a check for 'repo->initialized'. If \n> we're absolutely certain that the 'repo' is initialized, wouldn't it be \n> better to simply remove all the checks inside the getter and leave the \n> judgment to 'repo_config_values()'?\n\nYes, that was what I was getting at ;-).\n"},{"id":"546609","messageId":"18ad7c1c-5ddc-4f62-ba7c-5cda53f5a48d@malon.dev","threadId":"65873","inReplyTo":"xmqqbjcv2h3j.fsf@gitster.g","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-28T12:58:35Z","receivedAt":"2026-06-28T12:58:44Z","isPatch":true,"body":"On 6/28/26 16:40, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>>> Wouldn't we rather want to try to be more strict and say\n>>>\n>>> \tif (!repo || !repo->initialized)\n>>> \t\tBUG(\"repo must be an initialied repository\");\n>>>\n>>> here?  Aren't all the callers of this function supposed to be\n>>> dealing with an already initialized repository?\n>>\n>> That makes sense, but from my point of view...\n>>\n>> 'repo_config_values()' already has a check for 'repo->initialized'. If\n>> we're absolutely certain that the 'repo' is initialized, wouldn't it be\n>> better to simply remove all the checks inside the getter and leave the\n>> judgment to 'repo_config_values()'?\n> \n> Yes, that was what I was getting at ;-).\n\nA lot of CI tests are failing, but that just goes to show that the \n\"bugs\" are being properly identified, doesn’t it?\n\nIt means there are a lot of \"invalid\" calls in the tests (if the way we \ndefine a 'valid' call, i.e. repo must be initialized, is correct)... It \nseems that code like 'if (repo != the_repository) return' or something \nsimilar is inevitably going to end up somewhere, even though, as you \nsaid, it’s \"sweeping problems under the rug.\"\n\nI’m not sure how to proceed from here either..\n\nRegards, yuchen\n"},{"id":"546623","messageId":"CAP8UFD3Z0M_1NEXGcAxNZKpRUQiSkHZLTEvNNYushKA_PoPgjA@mail.gmail.com","threadId":"65873","inReplyTo":"18ad7c1c-5ddc-4f62-ba7c-5cda53f5a48d@malon.dev","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2026-06-29T06:03:40Z","receivedAt":"2026-06-29T06:03:53Z","isPatch":true,"body":"On Sun, Jun 28, 2026 at 2:58 PM Tian Yuchen <cat@malon.dev> wrote:\n>\n> On 6/28/26 16:40, Junio C Hamano wrote:\n> > Tian Yuchen <cat@malon.dev> writes:\n> >\n> >>> Wouldn't we rather want to try to be more strict and say\n> >>>\n> >>>     if (!repo || !repo->initialized)\n> >>>             BUG(\"repo must be an initialied repository\");\n> >>>\n> >>> here?  Aren't all the callers of this function supposed to be\n> >>> dealing with an already initialized repository?\n> >>\n> >> That makes sense, but from my point of view...\n> >>\n> >> 'repo_config_values()' already has a check for 'repo->initialized'. If\n> >> we're absolutely certain that the 'repo' is initialized, wouldn't it be\n> >> better to simply remove all the checks inside the getter and leave the\n> >> judgment to 'repo_config_values()'?\n> >\n> > Yes, that was what I was getting at ;-).\n>\n> A lot of CI tests are failing, but that just goes to show that the\n> \"bugs\" are being properly identified, doesn’t it?\n>\n> It means there are a lot of \"invalid\" calls in the tests (if the way we\n> define a 'valid' call, i.e. repo must be initialized, is correct)... It\n> seems that code like 'if (repo != the_repository) return' or something\n> similar is inevitably going to end up somewhere, even though, as you\n> said, it’s \"sweeping problems under the rug.\"\n>\n> I’m not sure how to proceed from here either..\n\nI agree that the best end state would be to have no `if (!repo ||\n!repo->initialized)` check, but we shouldn't have to get there right\naway. I think it's fine to proceed in several steps:\n\n1) `if (!repo || !repo->initialized) return NULL;` allows us to remove\nthe global variable and use getters which will help us in the next\nstep.\n\n2) `if (!repo || !repo->initialized) return BUG(\"repo must be an\ninitialized repository\");` now we want to find and fix callers\n(including tests) that haven't properly initialized things.\n\n3) No `if (!repo || !repo->initialized)` check, as we are sure that\nall the callers that didn't properly initialized things have been\nfound and fixed.\n\nSo I think 1) is fine for now as long as we properly explain in the\ncommit messages and in code comments (maybe using NEEDSWORK comments)\nthat we know there is more work to do on this in the future.\n"},{"id":"546672","messageId":"xmqqbjctz9mh.fsf@gitster.g","threadId":"65873","inReplyTo":"CAP8UFD3Z0M_1NEXGcAxNZKpRUQiSkHZLTEvNNYushKA_PoPgjA@mail.gmail.com","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-29T14:47:34Z","receivedAt":"2026-06-29T14:47:36Z","isPatch":true,"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> I agree that the best end state would be to have no `if (!repo ||\n> !repo->initialized)` check, but we shouldn't have to get there right\n> away. I think it's fine to proceed in several steps:\n>\n> 1) `if (!repo || !repo->initialized) return NULL;` allows us to remove\n> the global variable and use getters which will help us in the next\n> step.\n>\n> 2) `if (!repo || !repo->initialized) return BUG(\"repo must be an\n> initialized repository\");` now we want to find and fix callers\n> (including tests) that haven't properly initialized things.\n>\n> 3) No `if (!repo || !repo->initialized)` check, as we are sure that\n> all the callers that didn't properly initialized things have been\n> found and fixed.\n>\n> So I think 1) is fine for now as long as we properly explain in the\n> commit messages and in code comments (maybe using NEEDSWORK comments)\n> that we know there is more work to do on this in the future.\n\nI am OK with the progressive improvements, but if \"wean us away from\nglobal variables\" topic stops at step 1 I would have to say that is\na failed experiment.  Not doing (2) means you made the system bug-to-bug\ncompatible from the old world where these things weren't in repo-config\nbut were still globals, which is code churn for nothing good to show\nin the end result.  We need to get to (2) before declaring victory.\n\nBut of course, we need to start somewhere.  (1) with in-code\ncomments sprinkled to such places that say that we'd need to revisit\nwould be a good place to start.\n\nThanks.\n"},{"id":"546796","messageId":"054b3fb6-0e69-473d-9778-b1b11ea82b3a@malon.dev","threadId":"65873","inReplyTo":"xmqqbjctz9mh.fsf@gitster.g","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-30T16:20:42Z","receivedAt":"2026-06-30T16:21:06Z","isPatch":true,"body":"Hi Christian and Junio,\n\nOn 6/29/26 22:47, Junio C Hamano wrote:\n> Christian Couder <christian.couder@gmail.com> writes:\n> \n>> I agree that the best end state would be to have no `if (!repo ||\n>> !repo->initialized)` check, but we shouldn't have to get there right\n>> away. I think it's fine to proceed in several steps:\n>>\n>> 1) `if (!repo || !repo->initialized) return NULL;` allows us to remove\n>> the global variable and use getters which will help us in the next\n>> step.\n>>\n>> 2) `if (!repo || !repo->initialized) return BUG(\"repo must be an\n>> initialized repository\");` now we want to find and fix callers\n>> (including tests) that haven't properly initialized things.\n>>\n>> 3) No `if (!repo || !repo->initialized)` check, as we are sure that\n>> all the callers that didn't properly initialized things have been\n>> found and fixed.\n>>\n>> So I think 1) is fine for now as long as we properly explain in the\n>> commit messages and in code comments (maybe using NEEDSWORK comments)\n>> that we know there is more work to do on this in the future.\n> \n> I am OK with the progressive improvements, but if \"wean us away from\n> global variables\" topic stops at step 1 I would have to say that is\n> a failed experiment.  Not doing (2) means you made the system bug-to-bug\n> compatible from the old world where these things weren't in repo-config\n> but were still globals, which is code churn for nothing good to show\n> in the end result.  We need to get to (2) before declaring victory.\n> \n> But of course, we need to start somewhere.  (1) with in-code\n> comments sprinkled to such places that say that we'd need to revisit\n> would be a good place to start.\n> \n> Thanks.\n\nThank you both for paving a clear way forward.\n\nFor the upcoming V5 patch, I will implement:\n\n1. Revert to the shields ('return NULL' for the getter, and bypassing \n'repo != repository' for the _clear()).\n2. Add 'NEEDSWORK' comments above them documenting that these are \ntemporary changes.\n3. Update the commit message to reflect this.\n\nOnce this initial migration safely lands, the next goal will be to \ninvestigate those failing CI tests.\n\nWill send out V5 shortly.\n\nRegards, yuchen\n\n\n"},{"id":"546797","messageId":"20260630164401.2906091-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260627160813.1074201-1-cat@malon.dev","subject":"[PATCH v5 0/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-30T16:44:00Z","receivedAt":"2026-06-30T16:44:15Z","isPatch":true,"body":"This patch continues the libification effort by migrating the global\nstring variable 'excludes_file' into 'struct repo_config_values'. Since\nthis is a dynamically allocated variable, the migration requires proper\nheap memory management.\n\nThis patch mainly does three things:\n\n - Abstract the XDG fallback lazy-loading logic out of dir.c into a proper\n getter.\n\n - Move the variables into the struct repo_config_values.\n\n - Introduce the memory destructor 'repo_config_values_clear()'.\n\nChanges since V4:\n\nDefensive checks are retained in both the getter (returning NULL if\nuninitialized) and the destructor (bypassing non-the_repository instances)\nto maintain bug-to-bug compatibility. These are marked with 'NEEDSWORK'\ncomments.\n\nFuture work will track down and fix the offending callers to eventually\nreplace these shields with stricter BUG() assertions. [1]\n\nTHANKS!\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n\n[1] https://lore.kernel.org/git/054b3fb6-0e69-473d-9778-b1b11ea82b3a@malon.dev/T/#mca0730b6735298316ec00a53d4719935d18837a6\n\nTian Yuchen (1):\n  environment: move excludes_file into repo_config_values\n\n dir.c         |  4 ++--\n environment.c | 43 ++++++++++++++++++++++++++++++++++++++++---\n environment.h | 13 ++++++++++++-\n repository.c  |  1 +\n 4 files changed, 55 insertions(+), 6 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"546798","messageId":"20260630164401.2906091-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260630164401.2906091-1-cat@malon.dev","subject":"[PATCH v5 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-30T16:44:01Z","receivedAt":"2026-06-30T16:44:19Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL, 'setup_standard_excludes()'\nin 'dir.c' forcefully evaluates and assigns the XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. Introduce repo_config_values_clear()\nand wire it up in 'repo_clear()' to safely free this memory when a\nrepository instance is destroyed. Also clean up the heap-allocated\n'attributes_file' in this new destructor while we are at it.\n\nNote on transition:\n\nDefensive checks are temporarily retained in both the getter (returning\nNULL if uninitialized) and the destructor (bypassing non-the_repository\ninstances) to maintain bug-to-bug compatibility. These are marked with\nNEEDSWORK comments. Future work will track down and fix the offending\ncallers to eventually replace these shields with stricter BUG()\nassertions.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 43 ++++++++++++++++++++++++++++++++++++++++---\n environment.h | 13 ++++++++++++-\n repository.c  |  1 +\n 4 files changed, 55 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..5fc13d47c2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,24 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\t/*\n+\t * NEEDSWORK: This is a temporary shield to maintain bug-to-bug\n+\t * compatibility during the libification transition.\n+\t *\n+\t * Once offending callers are properly fixed, this check should\n+\t * be upgraded to a BUG() assertion and eventually removed entirely.\n+\t */\n+\tif (!repo || !repo->initialized)\n+\t\treturn NULL;\n+\n+\tif (!repo_config_values(repo)->excludes_file)\n+\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn repo_config_values(repo)->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +478,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +732,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -726,3 +744,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg;\n+\n+\t/*\n+\t * NEEDSWORK: Temporary shield to prevent temporary/uninitialized\n+\t * submodules from triggering the BUG() at repository.c:59\n+\t * during repo_clear(). This should be removed once submodule\n+\t * lifecycle and per-repo config support are fully resolved.\n+\t */\n+\tif (repo != the_repository)\n+\t\treturn;\n+\n+\tcfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..2e8352de7f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,8 +134,19 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repository *repo);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\n@@ -208,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b31f1b7852 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(repo);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"546931","messageId":"20260701180813.776173-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260630164401.2906091-1-cat@malon.dev","subject":"[PATCH v6 0/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-01T18:08:11Z","receivedAt":"2026-07-01T18:08:28Z","isPatch":true,"body":"This patch continues the libification effort by migrating the global\nstring variable 'excludes_file' into 'struct repo_config_values'. Since\nthis is a dynamically allocated variable, the migration requires proper\nheap memory management.\n\nThis patch mainly does three things:\n\n - Abstract the XDG fallback lazy-loading logic out of dir.c into a proper\n getter.\n\n - Move the variables into the struct repo_config_values.\n\n - Introduce the memory destructor 'repo_config_values_clear()'.\n\nChanges since V5:\n\nDrop the defensive check in 'repo_excludes_file()'.\n\nAll known test errors that occur *without* defensive checks are intercepted\nby the 'repo != the_repository' check when checks *are there*, rather than\nby the '!repo->initialized' check. In other words, the getter actually doesn't\nneed any checks added, because all of its calls are safe. We just need to\nkeep the check and the 'NEEDSWORK' comment *on the destructor* until\n'repo_config_values()' eventually supports submodules, at which point we can\nremove them.\n\nTHANKS!\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n\nTian Yuchen (1):\n  environment: move excludes_file into repo_config_values\n\n dir.c         |  4 ++--\n environment.c | 33 ++++++++++++++++++++++++++++++---\n environment.h | 13 ++++++++++++-\n repository.c  |  1 +\n 4 files changed, 45 insertions(+), 6 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"546932","messageId":"20260701180813.776173-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260701180813.776173-1-cat@malon.dev","subject":"[PATCH v6 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-01T18:08:12Z","receivedAt":"2026-07-01T18:08:30Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL, 'setup_standard_excludes()'\nin 'dir.c' forcefully evaluates and assigns the XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. Introduce repo_config_values_clear()\nand wire it up in 'repo_clear()' to safely free this memory when a\nrepository instance is destroyed. Also clean up the heap-allocated\n'attributes_file' in this new destructor while we are at it.\n\nNote on transition:\n\nSubmodules are currently not supported by repo_config_values().\nSince repo_clear() cleans up all repository instances (including\nsubmodules), we must bypass them in repo_config_values_clear() to\nprevent hitting the BUG() in repository.c:59. In the future when\nsubmodules are supported, this check should be removed.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 33 ++++++++++++++++++++++++++++++---\n environment.h | 13 ++++++++++++-\n repository.c  |  1 +\n 4 files changed, 45 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..f1f859dd08 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,14 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tif (!repo_config_values(repo)->excludes_file)\n+\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn repo_config_values(repo)->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +468,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +722,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -726,3 +734,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg;\n+\n+\t/*\n+\t * NEEDSWORK: Submodules are currently not supported by\n+\t * repo_config_values(). Since repo_clear() cleans up all\n+\t * repository instances (including submodules), we must bypass\n+\t * them here to prevent hitting the BUG() in repository.c:59.\n+\t */\n+\tif (repo != the_repository)\n+\t\treturn;\n+\n+\tcfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..2e8352de7f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,8 +134,19 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repository *repo);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\n@@ -208,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b31f1b7852 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(repo);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"546933","messageId":"0462af01-3b81-4c72-9c4e-5124c6f1ce02@malon.dev","threadId":"65873","inReplyTo":"054b3fb6-0e69-473d-9778-b1b11ea82b3a@malon.dev","subject":"Re: [PATCH v4 1/1] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-01T18:14:36Z","receivedAt":"2026-07-01T18:14:43Z","isPatch":true,"body":"On 7/1/26 00:20, Tian Yuchen wrote:\n> \n> Thank you both for paving a clear way forward.\n> \n> For the upcoming V5 patch, I will implement:\n> \n> 1. Revert to the shields ('return NULL' for the getter, and bypassing \n> 'repo != repository' for the _clear()).\n> 2. Add 'NEEDSWORK' comments above them documenting that these are \n> temporary changes.\n> 3. Update the commit message to reflect this.\n> \n> Once this initial migration safely lands, the next goal will be to \n> investigate those failing CI tests.\n> \n> Will send out V5 shortly.\n> \n> Regards, yuchen\n> \n\nSorry, I forgot to test the repo_excludes_file() and \nrepo_config_values_clear() with/without checks separately earlier. As \nfor repo_excludes_files(), I found that the check wasn't necessary, so I \nremoved it in v6.\n\nIn other words, we're now only filtering out calls that aren't passing \n'the_repository' to _clear(), which is still a task to be addressed in \nthe future. I think the current situation should be fairly satisfactory.\n\nHope this helps.\n\nRegards, yuchen\n\n"},{"id":"547235","messageId":"20260706142530.3681520-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260701180813.776173-1-cat@malon.dev","subject":"[PATCH v7 0/9] migrate more variables into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:21Z","receivedAt":"2026-07-06T14:25:54Z","isPatch":true,"body":"Hi everyone,\n\nThis patch series continues the ongoing libification effort by migrating\na batch of global configuration variables into struct repo_config_values.\n\nWhat does this series do:\n\ninfrastructure & strings (commits 1-6):\nIntroduce 'repo_config_values_clear()' to manage the lifecycle\nof heap-allocated configuration strings. This infrastructure is utilized\nto migrate string variables, including 'excludes_file', 'apply' whitespace\nconfigs, and external programs including 'editor', 'pager', 'askpass'.\n\nenums (commits 7-9):\nMigrate enumerations 'push_default', 'autorebase', and\n'object_creation_mode'. Care was taken to make these types available\nto the configuration structure without triggering circular header\ndependencies.\n\nRFC:\n\nCommit 3~5. Is it really necessary to migrate _program variables?\nhttps://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n\nCommit 6~9. Previous related discussions on 'git_branch_track'.\nhttps://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n\nNote:\n\nSince a new getter 'repo_excludes_file()' is introduced, as previously\npromised, once it is finally merged into 'master', there will be a patch to\nupdate and squash the comments.\n\nSimilarly, I've noticed that the classification and sorting of variables in\n'repo_config_values' don't seem to be correct. There will also be a patch\nto fix this, and I think it will form a commit series along with the comment\npatch?\n\nChanges since v6:\n\nOnly the first two commits in this patch overlap with v6. The reason the\nsubsequent commits were not released separately is that Christian suggested\nplacing the introduction of 'repo_config_values_clear()' as a standalone\ncommit at the very beginning.\n\nIn other words, the v6 structure has been discarded, and this series is\nbeing released as almost a new patch.\n\nThanks!\n\nTian Yuchen (9):\n  repository: introduce repo_config_values_clear()\n  environment: move excludes_file into repo_config_values\n  environment: move editor_program into repo_config_values\n  environment: move pager_program into repo_config_values\n  environment: move askpass_program into repo_config_values\n  environment: migrate apply_default_whitespace and\n    apply_default_ignorewhitespace\n  environment: move push_default into repo_config_values\n  environment: move autorebase into repo_config_values\n  environment: move object_creation_mode into repo_config_values\n\n apply.c        | 20 +++++++-----\n branch.c       |  2 +-\n builtin/push.c |  8 ++---\n dir.c          |  4 +--\n editor.c       |  4 +--\n environment.c  | 87 +++++++++++++++++++++++++++++++++++---------------\n environment.h  | 75 +++++++++++++++++++++++++++----------------\n object-file.c  |  2 +-\n pager.c        | 17 ++++++----\n prompt.c       |  3 +-\n remote.c       |  2 +-\n repository.c   |  1 +\n 12 files changed, 145 insertions(+), 80 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"547236","messageId":"20260706142530.3681520-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 1/9] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:22Z","receivedAt":"2026-07-06T14:25:57Z","isPatch":true,"body":"As part of the ongoing libification effort, dynamically allocated\nglobal configuration variables are being moved into\n'struct repo_config_values'. To prevent memory leaks, we need a\ndestructor to free these heap-allocated variables when a repository\ninstance is torn down.\n\nIntroduce 'repo_config_values_clear()' in environment.c and invoke it\nfrom 'repo_clear()' in repository.c. As a starting point, update this\nnew function to handle the cleanup of 'attributes_file'.\n\nNote:\n\nSubmodules are currently not supported by repo_config_values(), which\nexplicitly BUG()s out if 'repo != the_repository'. Since repo_clear()\ncleans up all repository instances, we must bypass them to prevent\ncrashing.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 19 +++++++++++++++++++\n environment.h |  9 +++++++++\n repository.c  |  1 +\n 3 files changed, 29 insertions(+)\n\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..13677484de 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -726,3 +726,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg;\n+\n+\t/*\n+\t * NEEDSWORK: Submodules are currently not supported by\n+\t * repo_config_values(), which explicitly BUG()s out if\n+\t * repo != the_repository. Since repo_clear() cleans up all\n+\t * repository instances, we must bypass them here to prevent\n+\t * crashing.\n+\t */\n+\tif (repo != the_repository)\n+\t\treturn;\n+\n+\tcfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->attributes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..c4a6a45704 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n \n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repository *repo);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b31f1b7852 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(repo);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"547237","messageId":"20260706142530.3681520-3-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 2/9] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:23Z","receivedAt":"2026-07-06T14:26:02Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL,\n'setup_standard_excludes()'\nin 'dir.c' forcefully evaluates and assigns the XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. It is safely freed using the newly\nintroduced `repo_config_values_clear()` function when the repository\nis torn down.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 15 ++++++++++++---\n environment.h |  4 +++-\n 3 files changed, 17 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex 13677484de..5950592d63 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,14 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tif (!repo_config_values(repo)->excludes_file)\n+\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn repo_config_values(repo)->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +468,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +722,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -744,4 +752,5 @@ void repo_config_values_clear(struct repository *repo)\n \tcfg = repo_config_values(repo);\n \n \tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n }\ndiff --git a/environment.h b/environment.h\nindex c4a6a45704..2e8352de7f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\n-- \n2.43.0\n\n"},{"id":"547238","messageId":"20260706142530.3681520-4-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 3/9] environment: move editor_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:24Z","receivedAt":"2026-07-06T14:26:05Z","isPatch":true,"body":"The global variable 'editor_program' holds the path to the user's\npreferred editor. Move 'editor_program' into\n'struct repo_config_values' to continue the libification effort.\n\nThere have been discussions on whether external programs like\neditors truly need to be configured on a per-repository basis within\nthe same process. While a single process might rarely invoke\ndifferent editors, this migration is necessary for two reasons:\n\n1. Developers frequently use different toolchains for different\n   projects. Per-repo configuration respects this.\n\n2. Moving this string into 'repo_config_values' eliminates mutable\n   global state. As the codebase moves toward becoming a long-running\n   processes managing multiple repositories concurrently must\n   not overwrite each other's program configurations.\n\nNo standalone getter function is introduced. Callers directly access\nthe field via 'repo_config_values()'. Heap memory is safely reclaimed\nin 'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n editor.c      | 4 ++--\n environment.c | 7 ++++---\n environment.h | 2 +-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/editor.c b/editor.c\nindex fd174e6a03..07d264cba0 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -29,8 +29,8 @@ const char *git_editor(void)\n \tconst char *editor = getenv(\"GIT_EDITOR\");\n \tint terminal_is_dumb = is_terminal_dumb();\n \n-\tif (!editor && editor_program)\n-\t\teditor = editor_program;\n+\tif (!editor && repo_config_values(the_repository)->editor_program)\n+\t\teditor = repo_config_values(the_repository)->editor_program;\n \tif (!editor && !terminal_is_dumb)\n \t\teditor = getenv(\"VISUAL\");\n \tif (!editor)\ndiff --git a/environment.c b/environment.c\nindex 5950592d63..0a01f4761a 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *editor_program;\n char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n@@ -435,8 +434,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.editor\")) {\n-\t\tFREE_AND_NULL(editor_program);\n-\t\treturn git_config_string(&editor_program, var, value);\n+\t\tFREE_AND_NULL(cfg->editor_program);\n+\t\treturn git_config_string(&cfg->editor_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n@@ -723,6 +722,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n+\tcfg->editor_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -753,4 +753,5 @@ void repo_config_values_clear(struct repository *repo)\n \n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n+\tFREE_AND_NULL(cfg->editor_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 2e8352de7f..1ec19149cb 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -91,6 +91,7 @@ struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n \tchar *excludes_file;\n+\tchar *editor_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *editor_program;\n extern char *askpass_program;\n \n /*\n-- \n2.43.0\n\n"},{"id":"547239","messageId":"20260706142530.3681520-5-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 4/9] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:25Z","receivedAt":"2026-07-06T14:26:09Z","isPatch":true,"body":"The 'pager_program' variable is currently defined as a file-scoped\nstatic string in pager.c. Move it into 'struct repo_config_values'.\n\nThe configuration parsing logic remains strictly within pager.c to\nrespect subsystem boundaries. The read/write operations are simply\nredirected to the repository-specific structure using\n'repo_config_values()'.\n\nSimilar to the recent editor_program migration, no standalone getter\nis introduced to keep the code minimal. The dynamically allocated\nmemory is now managed by 'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  2 ++\n environment.h |  1 +\n pager.c       | 17 ++++++++++-------\n 3 files changed, 13 insertions(+), 7 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 0a01f4761a..a1204fdcb2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -723,6 +723,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n+\tcfg->pager_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -754,4 +755,5 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n+\tFREE_AND_NULL(cfg->pager_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 1ec19149cb..22f6697c52 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tchar *excludes_file;\n \tchar *editor_program;\n+\tchar *pager_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/pager.c b/pager.c\nindex 35b210e048..450ad053b6 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -5,6 +5,8 @@\n #include \"run-command.h\"\n #include \"sigchain.h\"\n #include \"alias.h\"\n+#include \"repository.h\"\n+#include \"environment.h\"\n \n int pager_use_color = 1;\n \n@@ -13,7 +15,6 @@ int pager_use_color = 1;\n #endif\n \n static struct child_process pager_process;\n-static char *pager_program;\n static int old_fd1 = -1, old_fd2 = -1;\n \n /* Is the value coming back from term_columns() just a guess? */\n@@ -75,10 +76,12 @@ static void wait_for_pager_signal(int signo)\n \n static int core_pager_config(const char *var, const char *value,\n \t\t\t     const struct config_context *ctx UNUSED,\n-\t\t\t     void *data UNUSED)\n+\t\t\t     void *data)\n {\n+\tstruct repository *r = data;\n+\n \tif (!strcmp(var, \"core.pager\"))\n-\t\treturn git_config_string(&pager_program, var, value);\n+\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n \treturn 0;\n }\n \n@@ -91,10 +94,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n \n \tpager = getenv(\"GIT_PAGER\");\n \tif (!pager) {\n-\t\tif (!pager_program)\n+\t\tif (!repo_config_values(r)->pager_program)\n \t\t\tread_early_config(r,\n-\t\t\t\t\t  core_pager_config, NULL);\n-\t\tpager = pager_program;\n+\t\t\t\t\t  core_pager_config, r);\n+\t\tpager = repo_config_values(r)->pager_program;\n \t}\n \tif (!pager)\n \t\tpager = getenv(\"PAGER\");\n@@ -303,6 +306,6 @@ int check_pager_config(struct repository *r, const char *cmd)\n \tread_early_config(r, pager_command_config, &data);\n \n \tif (data.value)\n-\t\tpager_program = data.value;\n+\t\trepo_config_values(r)->pager_program = data.value;\n \treturn data.want;\n }\n-- \n2.43.0\n\n"},{"id":"547240","messageId":"20260706142530.3681520-6-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 5/9] environment: move askpass_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:26Z","receivedAt":"2026-07-06T14:26:13Z","isPatch":true,"body":"The global variable 'askpass_program' stores the path to the program\nused to prompt the user for credentials. Move it into repo_config_values\nto continue the libification effort.\n\nWhile it is uncommon for a single process to require different askpass\nprograms for different repositories, maintaining this value as a mutable\nglobal string is a blocker for libification. Global heap-allocated\nstrings introduce thread-safety issues in a multi-repo environment.\n\nMove 'askpass_program' into 'struct repo_config_values' to eliminate\nthis global state. The memory is now safely managed and freed via\n'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 6 ++++--\n environment.h | 1 +\n prompt.c      | 3 ++-\n 3 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex a1204fdcb2..3782bf68aa 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -462,8 +462,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.askpass\")) {\n-\t\tFREE_AND_NULL(askpass_program);\n-\t\treturn git_config_string(&askpass_program, var, value);\n+\t\tFREE_AND_NULL(cfg->askpass_program);\n+\t\treturn git_config_string(&cfg->askpass_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n@@ -724,6 +724,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n+\tcfg->askpass_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -756,4 +757,5 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n+\tFREE_AND_NULL(cfg->askpass_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 22f6697c52..d55b1ba073 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -93,6 +93,7 @@ struct repo_config_values {\n \tchar *excludes_file;\n \tchar *editor_program;\n \tchar *pager_program;\n+\tchar *askpass_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/prompt.c b/prompt.c\nindex 706fba2a50..d8d74c7e37 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -3,6 +3,7 @@\n #include \"git-compat-util.h\"\n #include \"parse.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"prompt.h\"\n@@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)\n \n \t\taskpass = getenv(\"GIT_ASKPASS\");\n \t\tif (!askpass)\n-\t\t\taskpass = askpass_program;\n+\t\t\taskpass = repo_config_values(the_repository)->askpass_program;\n \t\tif (!askpass)\n \t\t\taskpass = getenv(\"SSH_ASKPASS\");\n \t\tif (askpass && *askpass)\n-- \n2.43.0\n\n"},{"id":"547241","messageId":"20260706142530.3681520-7-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 6/9] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:27Z","receivedAt":"2026-07-06T14:26:19Z","isPatch":true,"body":"The global variables 'apply_default_whitespace' and\n'apply_default_ignorewhitespace' are used to store the default\nwhitespace configuration for 'git apply'. Move these variables\ninto 'struct repo_config_values' to continue the libification\neffort.\n\nDynamically allocated strings fetched via 'repo_config_get_string()'\nare now tracked per-repository and safely freed in\n'repo_config_values_clear()'.\n\nAs part of this transition, update 'git_apply_config()' to accept a\n'struct repository *' argument rather than relying on the\n'the_repository' global.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n apply.c       | 20 ++++++++++++--------\n environment.c |  6 ++++--\n environment.h |  4 ++--\n 3 files changed, 18 insertions(+), 12 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..66db9b7678 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -47,11 +47,13 @@ struct gitdiff_data {\n \tint p_value;\n };\n \n-static void git_apply_config(void)\n+static void git_apply_config(struct repository *repo)\n {\n-\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n-\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n-\trepo_config(the_repository, git_xmerge_config, NULL);\n+\trepo_config_get_string(repo, \"apply.whitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_whitespace);\n+\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_ignorewhitespace);\n+\trepo_config(repo, git_xmerge_config, NULL);\n }\n \n static int parse_whitespace_option(struct apply_state *state, const char *option)\n@@ -126,10 +128,12 @@ int init_apply_state(struct apply_state *state,\n \tstrset_init(&state->kept_symlinks);\n \tstrbuf_init(&state->root, 0);\n \n-\tgit_apply_config();\n-\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n+\tgit_apply_config(repo);\n+\tif (repo_config_values(repo)->apply_default_whitespace &&\n+\t    parse_whitespace_option(state, repo_config_values(repo)->apply_default_whitespace))\n \t\treturn -1;\n-\tif (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))\n+\tif (repo_config_values(repo)->apply_default_ignorewhitespace &&\n+\t    parse_ignorewhitespace_option(state, repo_config_values(repo)->apply_default_ignorewhitespace))\n \t\treturn -1;\n \treturn 0;\n }\n@@ -192,7 +196,7 @@ int check_apply_state(struct apply_state *state, int force_apply)\n \n static void set_default_whitespace_mode(struct apply_state *state)\n {\n-\tif (!state->whitespace_option && !apply_default_whitespace)\n+\tif (!state->whitespace_option && !repo_config_values(state->repo)->apply_default_whitespace)\n \t\tstate->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);\n }\n \ndiff --git a/environment.c b/environment.c\nindex 3782bf68aa..8744790219 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,8 +49,6 @@ int assume_unchanged;\n int is_bare_repository_cfg = -1; /* unspecified */\n char *git_commit_encoding;\n char *git_log_output_encoding;\n-char *apply_default_whitespace;\n-char *apply_default_ignorewhitespace;\n int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n@@ -725,6 +723,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n \tcfg->askpass_program = NULL;\n+\tcfg->apply_default_whitespace = NULL;\n+\tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -758,4 +758,6 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n \tFREE_AND_NULL(cfg->askpass_program);\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n }\ndiff --git a/environment.h b/environment.h\nindex d55b1ba073..9aecd64152 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -94,6 +94,8 @@ struct repo_config_values {\n \tchar *editor_program;\n \tchar *pager_program;\n \tchar *askpass_program;\n+\tchar *apply_default_whitespace;\n+\tchar *apply_default_ignorewhitespace;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -182,8 +184,6 @@ extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\n-extern char *apply_default_whitespace;\n-extern char *apply_default_ignorewhitespace;\n extern unsigned long pack_size_limit_cfg;\n \n extern int protect_hfs;\n-- \n2.43.0\n\n"},{"id":"547242","messageId":"20260706142530.3681520-8-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 7/9] environment: move push_default into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:28Z","receivedAt":"2026-07-06T14:26:24Z","isPatch":true,"body":"The global variable 'push_default' specifies the default behavior of\n'git push' when no explicit refspec is provided. Move 'push_default'\ninto 'struct repo_config_values' to continue the libification effort.\n\nWhile 'enum push_default_type' ideally belongs in 'remote.h', moving it\nthere introduces a circular dependency chain:\n\n  remote.h -> hash.h -> repository.h -> environment.h.\n\nTherefore, the enum definition is kept in 'environment.h' just above\n'struct repo_config_values' with a NEEDSWORK comment for future cleanup.\n\nModify the configuration parsing in environment.c to update the\nper-repository structure directly, and update caller across the\ncodebase to access the value via 'repo_config_values()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n builtin/push.c |  8 ++++----\n environment.c  | 16 +++++++++-------\n environment.h  | 26 ++++++++++++++++----------\n remote.c       |  2 +-\n 4 files changed, 30 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..6dc3224b60 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -88,7 +88,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,\n \t\t}\n \t}\n \n-\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n+\tif (repo_config_values(the_repository)->push_default == PUSH_DEFAULT_UPSTREAM &&\n \t    skip_prefix(matched->name, \"refs/heads/\", &branch_name)) {\n \t\tstruct branch *branch = branch_get(branch_name);\n \t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n@@ -160,7 +160,7 @@ static NORETURN void die_push_simple(struct branch *branch,\n \t * Don't show advice for people who explicitly set\n \t * push.default.\n \t */\n-\tif (push_default == PUSH_DEFAULT_UNSPECIFIED)\n+\tif (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)\n \t\tadvice_pushdefault_maybe = _(\"\\n\"\n \t\t\t\t \"To choose either option permanently, \"\n \t\t\t\t \"see push.default in 'git help config'.\\n\");\n@@ -232,7 +232,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tconst char *dst;\n \tint same_remote;\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tcase PUSH_DEFAULT_MATCHING:\n \t\trefspec_append(&rs, \":\");\n \t\treturn;\n@@ -252,7 +252,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tdst = branch->refname;\n \tsame_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tdefault:\n \tcase PUSH_DEFAULT_UNSPECIFIED:\n \tcase PUSH_DEFAULT_SIMPLE:\ndiff --git a/environment.c b/environment.c\nindex 8744790219..09de2fee87 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -59,7 +59,6 @@ enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n-enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -619,21 +618,23 @@ static int git_default_branch_config(const char *var, const char *value)\n \n static int git_default_push_config(const char *var, const char *value)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tif (!strcmp(var, \"push.default\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"nothing\"))\n-\t\t\tpush_default = PUSH_DEFAULT_NOTHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_NOTHING;\n \t\telse if (!strcmp(value, \"matching\"))\n-\t\t\tpush_default = PUSH_DEFAULT_MATCHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_MATCHING;\n \t\telse if (!strcmp(value, \"simple\"))\n-\t\t\tpush_default = PUSH_DEFAULT_SIMPLE;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_SIMPLE;\n \t\telse if (!strcmp(value, \"upstream\"))\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"tracking\")) /* deprecated */\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"current\"))\n-\t\t\tpush_default = PUSH_DEFAULT_CURRENT;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_CURRENT;\n \t\telse {\n \t\t\terror(_(\"malformed value for %s: %s\"), var, value);\n \t\t\treturn error(_(\"must be one of nothing, matching, simple, \"\n@@ -725,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->askpass_program = NULL;\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n+\tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 9aecd64152..72859b5d76 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -87,6 +87,21 @@ extern const char * const local_repo_env[];\n struct strvec;\n \n struct repository;\n+\n+/*\n+ * NEEDSWORK: It would be better if these definitions could be moved to\n+ * other more specific files, but care is needed to avoid circular\n+ * inclusion issues.\n+ */\n+enum push_default_type {\n+\tPUSH_DEFAULT_NOTHING = 0,\n+\tPUSH_DEFAULT_MATCHING,\n+\tPUSH_DEFAULT_SIMPLE,\n+\tPUSH_DEFAULT_UPSTREAM,\n+\tPUSH_DEFAULT_CURRENT,\n+\tPUSH_DEFAULT_UNSPECIFIED\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -96,6 +111,7 @@ struct repo_config_values {\n \tchar *askpass_program;\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n+\tenum push_default_type push_default;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -197,16 +213,6 @@ enum rebase_setup_type {\n };\n extern enum rebase_setup_type autorebase;\n \n-enum push_default_type {\n-\tPUSH_DEFAULT_NOTHING = 0,\n-\tPUSH_DEFAULT_MATCHING,\n-\tPUSH_DEFAULT_SIMPLE,\n-\tPUSH_DEFAULT_UPSTREAM,\n-\tPUSH_DEFAULT_CURRENT,\n-\tPUSH_DEFAULT_UNSPECIFIED\n-};\n-extern enum push_default_type push_default;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..d48c01d375 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo,\n \tif (remote->mirror)\n \t\treturn tracking_for_push_dest(remote, branch->refname, err);\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(repo)->push_default) {\n \tcase PUSH_DEFAULT_NOTHING:\n \t\treturn error_buf(err, _(\"push has no destination (push.default is 'nothing')\"));\n \n-- \n2.43.0\n\n"},{"id":"547243","messageId":"20260706142530.3681520-9-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 8/9] environment: move autorebase into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:29Z","receivedAt":"2026-07-06T14:26:27Z","isPatch":true,"body":"The global variable 'autorebase' dictates whether a newly created\nbranch should be configured to automatically rebase by default.\nMove it into 'struct repo_config_values' to continue the\nlibification effort.\n\nThe 'enum rebase_setup_type' definition is moved higher up in\n'environment.h' so that it is visible to the repository-specific\nstructure. The default state AUTOREBASE_NEVER is now correctly\ninitialized in 'repo_config_values_init()'.\n\nConfiguration parsing in 'git_default_branch_config()' is updated to\nwrite directly to the repository's configuration instance.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n branch.c      |  2 +-\n environment.c | 10 +++++-----\n environment.h | 16 ++++++++--------\n 3 files changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 243db7d0fc..e1c1f8c89d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n static int should_setup_rebase(const char *origin)\n {\n-\tswitch (autorebase) {\n+\tswitch (repo_config_values(the_repository)->autorebase) {\n \tcase AUTOREBASE_NEVER:\n \t\treturn 0;\n \tcase AUTOREBASE_LOCAL:\ndiff --git a/environment.c b/environment.c\nindex 09de2fee87..7701aa3bc0 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -58,7 +58,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n-enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -600,13 +599,13 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"never\"))\n-\t\t\tautorebase = AUTOREBASE_NEVER;\n+\t\t\tcfg->autorebase = AUTOREBASE_NEVER;\n \t\telse if (!strcmp(value, \"local\"))\n-\t\t\tautorebase = AUTOREBASE_LOCAL;\n+\t\t\tcfg->autorebase = AUTOREBASE_LOCAL;\n \t\telse if (!strcmp(value, \"remote\"))\n-\t\t\tautorebase = AUTOREBASE_REMOTE;\n+\t\t\tcfg->autorebase = AUTOREBASE_REMOTE;\n \t\telse if (!strcmp(value, \"always\"))\n-\t\t\tautorebase = AUTOREBASE_ALWAYS;\n+\t\t\tcfg->autorebase = AUTOREBASE_ALWAYS;\n \t\telse\n \t\t\treturn error(_(\"malformed value for %s\"), var);\n \t\treturn 0;\n@@ -727,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n+\tcfg->autorebase = AUTOREBASE_NEVER;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 72859b5d76..464ff73136 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -102,6 +102,13 @@ enum push_default_type {\n \tPUSH_DEFAULT_UNSPECIFIED\n };\n \n+enum rebase_setup_type {\n+\tAUTOREBASE_NEVER = 0,\n+\tAUTOREBASE_LOCAL,\n+\tAUTOREBASE_REMOTE,\n+\tAUTOREBASE_ALWAYS\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -112,6 +119,7 @@ struct repo_config_values {\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n+\tenum rebase_setup_type autorebase;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -205,14 +213,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum rebase_setup_type {\n-\tAUTOREBASE_NEVER = 0,\n-\tAUTOREBASE_LOCAL,\n-\tAUTOREBASE_REMOTE,\n-\tAUTOREBASE_ALWAYS\n-};\n-extern enum rebase_setup_type autorebase;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\n-- \n2.43.0\n\n"},{"id":"547244","messageId":"20260706142530.3681520-10-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v7 9/9] environment: move object_creation_mode into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-06T14:25:30Z","receivedAt":"2026-07-06T14:26:29Z","isPatch":true,"body":"The global variable 'object_creation_mode' controls how Git creates\nobject files, specifically determining whether to use hardlinks or\nrenames when moving temporary files into the object database. Move\nit into 'struct repo_config_values' to continue the libification\neffort.\n\nMove the 'enum object_creation_mode' definition higher up in\n'environment.h' to ensure it is visible to the structure. Initialize\nthe per-repository value to its default macro value\nOBJECT_CREATION_MODE inside 'repo_config_values_init()'.\n\nUpdate configuration parsing in 'git_default_core_config()' to write\ndirectly to the repository-specific configuration structure.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  6 +++---\n environment.h | 12 ++++++------\n object-file.c |  2 +-\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 7701aa3bc0..e50beda918 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -61,7 +61,6 @@ char *check_roundtrip_encoding;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n-enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;\n int grafts_keep_true_parents;\n unsigned long pack_size_limit_cfg;\n \n@@ -511,9 +510,9 @@ int git_default_core_config(const char *var, const char *value,\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\tif (!strcmp(value, \"rename\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_RENAMES;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES;\n \t\telse if (!strcmp(value, \"link\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n \t\telse\n \t\t\tdie(_(\"invalid mode for object creation: %s\"), value);\n \t\treturn 0;\n@@ -727,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->autorebase = AUTOREBASE_NEVER;\n+\tcfg->object_creation_mode = OBJECT_CREATION_MODE;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 464ff73136..eaa0aba7bc 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -109,6 +109,11 @@ enum rebase_setup_type {\n \tAUTOREBASE_ALWAYS\n };\n \n+enum object_creation_mode {\n+\tOBJECT_CREATION_USES_HARDLINKS = 0,\n+\tOBJECT_CREATION_USES_RENAMES = 1\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -120,6 +125,7 @@ struct repo_config_values {\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n \tenum rebase_setup_type autorebase;\n+\tenum object_creation_mode object_creation_mode;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -213,12 +219,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum object_creation_mode {\n-\tOBJECT_CREATION_USES_HARDLINKS = 0,\n-\tOBJECT_CREATION_USES_RENAMES = 1\n-};\n-extern enum object_creation_mode object_creation_mode;\n-\n extern int grafts_keep_true_parents;\n \n const char *get_log_output_encoding(void);\ndiff --git a/object-file.c b/object-file.c\nindex 9afa842da2..cbbfc8f1dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -415,7 +415,7 @@ int finalize_object_file_flags(struct repository *repo,\n retry:\n \tret = 0;\n \n-\tif (object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n+\tif (repo_config_values(repo)->object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n \t\tgoto try_rename;\n \telse if (link(tmpfile, filename))\n \t\tret = errno;\n-- \n2.43.0\n\n"},{"id":"547254","messageId":"xmqqpl10auhw.fsf@gitster.g","threadId":"65873","inReplyTo":"20260706142530.3681520-5-cat@malon.dev","subject":"Re: [PATCH v7 4/9] environment: move pager_program into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-06T17:38:35Z","receivedAt":"2026-07-06T17:38:37Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n>  \tif (data.value)\n> -\t\tpager_program = data.value;\n> +\t\trepo_config_values(r)->pager_program = data.value;\n>  \treturn data.want;\n>  }\n\nMay not be a new problem, but does the old value or pager_program\nleak here, if callers call this function more than once (or\npager_program gets assigned elsewhere)?\n"},{"id":"547496","messageId":"20260708160300.8852-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260706142530.3681520-1-cat@malon.dev","subject":"[PATCH v8 0/9] migrate more variables into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:51Z","receivedAt":"2026-07-08T16:03:15Z","isPatch":true,"body":"Hi everyone,\n\nThis patch series continues the ongoing libification effort by migrating\na batch of global configuration variables into struct repo_config_values.\n\nWhat does this series do:\n\ninfrastructure & strings (commits 1-6):\nIntroduce 'repo_config_values_clear()' to manage the lifecycle\nof heap-allocated configuration strings. This infrastructure is utilized\nto migrate string variables, including 'excludes_file', 'apply' whitespace\nconfigs, and external programs including 'editor', 'pager', 'askpass'.\n\nenums (commits 7-9):\nMigrate enumerations 'push_default', 'autorebase', and\n'object_creation_mode'. Care was taken to make these types available\nto the configuration structure without triggering circular header\ndependencies.\n\nRFC:\n\nCommit 3~5. Is it really necessary to migrate _program variables?\nhttps://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n\nCommit 6~9. Previous related discussions on 'git_branch_track'.\nhttps://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n\nNote:\n\nSince a new getter 'repo_excludes_file()' is introduced, as previously\npromised, once it is finally merged into 'master', there will be a patch to\nupdate and squash the comments.\n\nSimilarly, I've noticed that the classification and sorting of variables in\n'repo_config_values' don't seem to be correct. There will also be a patch\nto fix this, and I think it will form a commit series along with the comment\npatch?\n\nChange since v7:\n\nFixed a memory leak in pager.c.\n\nThanks!\n\nTian Yuchen (9):\n  repository: introduce repo_config_values_clear()\n  environment: move excludes_file into repo_config_values\n  environment: move editor_program into repo_config_values\n  environment: move pager_program into repo_config_values\n  environment: move askpass_program into repo_config_values\n  environment: migrate apply_default_whitespace and\n    apply_default_ignorewhitespace\n  environment: move push_default into repo_config_values\n  environment: move autorebase into repo_config_values\n  environment: move object_creation_mode into repo_config_values\n\n apply.c        | 20 +++++++-----\n branch.c       |  2 +-\n builtin/push.c |  8 ++---\n dir.c          |  4 +--\n editor.c       |  4 +--\n environment.c  | 87 +++++++++++++++++++++++++++++++++++---------------\n environment.h  | 75 +++++++++++++++++++++++++++----------------\n object-file.c  |  2 +-\n pager.c        | 21 +++++++-----\n prompt.c       |  3 +-\n remote.c       |  2 +-\n repository.c   |  1 +\n 12 files changed, 148 insertions(+), 81 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"547497","messageId":"20260708160300.8852-3-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 2/9] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:53Z","receivedAt":"2026-07-08T16:03:18Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL,\n'setup_standard_excludes()'\nin 'dir.c' forcefully evaluates and assigns the XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. It is safely freed using the newly\nintroduced `repo_config_values_clear()` function when the repository\nis torn down.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 15 ++++++++++++---\n environment.h |  4 +++-\n 3 files changed, 17 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex 13677484de..5950592d63 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,14 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tif (!repo_config_values(repo)->excludes_file)\n+\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn repo_config_values(repo)->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +468,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +722,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -744,4 +752,5 @@ void repo_config_values_clear(struct repository *repo)\n \tcfg = repo_config_values(repo);\n \n \tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n }\ndiff --git a/environment.h b/environment.h\nindex c4a6a45704..2e8352de7f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\n-- \n2.43.0\n\n"},{"id":"547498","messageId":"20260708160300.8852-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 1/9] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:52Z","receivedAt":"2026-07-08T16:03:19Z","isPatch":true,"body":"As part of the ongoing libification effort, dynamically allocated\nglobal configuration variables are being moved into\n'struct repo_config_values'. To prevent memory leaks, we need a\ndestructor to free these heap-allocated variables when a repository\ninstance is torn down.\n\nIntroduce 'repo_config_values_clear()' in environment.c and invoke it\nfrom 'repo_clear()' in repository.c. As a starting point, update this\nnew function to handle the cleanup of 'attributes_file'.\n\nNote:\n\nSubmodules are currently not supported by repo_config_values(), which\nexplicitly BUG()s out if 'repo != the_repository'. Since repo_clear()\ncleans up all repository instances, we must bypass them to prevent\ncrashing.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 19 +++++++++++++++++++\n environment.h |  9 +++++++++\n repository.c  |  1 +\n 3 files changed, 29 insertions(+)\n\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..13677484de 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -726,3 +726,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg;\n+\n+\t/*\n+\t * NEEDSWORK: Submodules are currently not supported by\n+\t * repo_config_values(), which explicitly BUG()s out if\n+\t * repo != the_repository. Since repo_clear() cleans up all\n+\t * repository instances, we must bypass them here to prevent\n+\t * crashing.\n+\t */\n+\tif (repo != the_repository)\n+\t\treturn;\n+\n+\tcfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->attributes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..c4a6a45704 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n \n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repository *repo);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b31f1b7852 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(repo);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"547499","messageId":"20260708160300.8852-4-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 3/9] environment: move editor_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:54Z","receivedAt":"2026-07-08T16:03:21Z","isPatch":true,"body":"The global variable 'editor_program' holds the path to the user's\npreferred editor. Move 'editor_program' into\n'struct repo_config_values' to continue the libification effort.\n\nThere have been discussions on whether external programs like\neditors truly need to be configured on a per-repository basis within\nthe same process. While a single process might rarely invoke\ndifferent editors, this migration is necessary for two reasons:\n\n1. Developers frequently use different toolchains for different\n   projects. Per-repo configuration respects this.\n\n2. Moving this string into 'repo_config_values' eliminates mutable\n   global state. As the codebase moves toward becoming a long-running\n   processes managing multiple repositories concurrently must\n   not overwrite each other's program configurations.\n\nNo standalone getter function is introduced. Callers directly access\nthe field via 'repo_config_values()'. Heap memory is safely reclaimed\nin 'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n editor.c      | 4 ++--\n environment.c | 7 ++++---\n environment.h | 2 +-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/editor.c b/editor.c\nindex fd174e6a03..07d264cba0 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -29,8 +29,8 @@ const char *git_editor(void)\n \tconst char *editor = getenv(\"GIT_EDITOR\");\n \tint terminal_is_dumb = is_terminal_dumb();\n \n-\tif (!editor && editor_program)\n-\t\teditor = editor_program;\n+\tif (!editor && repo_config_values(the_repository)->editor_program)\n+\t\teditor = repo_config_values(the_repository)->editor_program;\n \tif (!editor && !terminal_is_dumb)\n \t\teditor = getenv(\"VISUAL\");\n \tif (!editor)\ndiff --git a/environment.c b/environment.c\nindex 5950592d63..0a01f4761a 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *editor_program;\n char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n@@ -435,8 +434,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.editor\")) {\n-\t\tFREE_AND_NULL(editor_program);\n-\t\treturn git_config_string(&editor_program, var, value);\n+\t\tFREE_AND_NULL(cfg->editor_program);\n+\t\treturn git_config_string(&cfg->editor_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n@@ -723,6 +722,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n+\tcfg->editor_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -753,4 +753,5 @@ void repo_config_values_clear(struct repository *repo)\n \n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n+\tFREE_AND_NULL(cfg->editor_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 2e8352de7f..1ec19149cb 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -91,6 +91,7 @@ struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n \tchar *excludes_file;\n+\tchar *editor_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *editor_program;\n extern char *askpass_program;\n \n /*\n-- \n2.43.0\n\n"},{"id":"547500","messageId":"20260708160300.8852-5-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 4/9] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:55Z","receivedAt":"2026-07-08T16:03:25Z","isPatch":true,"body":"The 'pager_program' variable is currently defined as a file-scoped\nstatic string in pager.c. Move it into 'struct repo_config_values'.\n\nThe configuration parsing logic remains strictly within pager.c to\nrespect subsystem boundaries. The read/write operations are simply\nredirected to the repository-specific structure using\n'repo_config_values()'.\n\nSimilar to the recent editor_program migration, no standalone getter\nis introduced to keep the code minimal. The dynamically allocated\nmemory is now managed by 'repo_config_values_clear()'.\n\nOn top of that, fix a memory leak in pager.c while we are at it.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  2 ++\n environment.h |  1 +\n pager.c       | 21 +++++++++++++--------\n 3 files changed, 16 insertions(+), 8 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 0a01f4761a..a1204fdcb2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -723,6 +723,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n+\tcfg->pager_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -754,4 +755,5 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n+\tFREE_AND_NULL(cfg->pager_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 1ec19149cb..22f6697c52 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tchar *excludes_file;\n \tchar *editor_program;\n+\tchar *pager_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/pager.c b/pager.c\nindex 35b210e048..c8ebdd4b31 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -5,6 +5,8 @@\n #include \"run-command.h\"\n #include \"sigchain.h\"\n #include \"alias.h\"\n+#include \"repository.h\"\n+#include \"environment.h\"\n \n int pager_use_color = 1;\n \n@@ -13,7 +15,6 @@ int pager_use_color = 1;\n #endif\n \n static struct child_process pager_process;\n-static char *pager_program;\n static int old_fd1 = -1, old_fd2 = -1;\n \n /* Is the value coming back from term_columns() just a guess? */\n@@ -75,10 +76,12 @@ static void wait_for_pager_signal(int signo)\n \n static int core_pager_config(const char *var, const char *value,\n \t\t\t     const struct config_context *ctx UNUSED,\n-\t\t\t     void *data UNUSED)\n+\t\t\t     void *data)\n {\n+\tstruct repository *r = data;\n+\n \tif (!strcmp(var, \"core.pager\"))\n-\t\treturn git_config_string(&pager_program, var, value);\n+\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n \treturn 0;\n }\n \n@@ -91,10 +94,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n \n \tpager = getenv(\"GIT_PAGER\");\n \tif (!pager) {\n-\t\tif (!pager_program)\n+\t\tif (!repo_config_values(r)->pager_program)\n \t\t\tread_early_config(r,\n-\t\t\t\t\t  core_pager_config, NULL);\n-\t\tpager = pager_program;\n+\t\t\t\t\t  core_pager_config, r);\n+\t\tpager = repo_config_values(r)->pager_program;\n \t}\n \tif (!pager)\n \t\tpager = getenv(\"PAGER\");\n@@ -302,7 +305,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n \n \tread_early_config(r, pager_command_config, &data);\n \n-\tif (data.value)\n-\t\tpager_program = data.value;\n+\tif (data.value) {\n+\t\tfree(repo_config_values(r)->pager_program);\n+\t\trepo_config_values(r)->pager_program = data.value;\n+\t}\n \treturn data.want;\n }\n-- \n2.43.0\n\n"},{"id":"547501","messageId":"20260708160300.8852-6-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 5/9] environment: move askpass_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:56Z","receivedAt":"2026-07-08T16:03:29Z","isPatch":true,"body":"The global variable 'askpass_program' stores the path to the program\nused to prompt the user for credentials. Move it into repo_config_values\nto continue the libification effort.\n\nWhile it is uncommon for a single process to require different askpass\nprograms for different repositories, maintaining this value as a mutable\nglobal string is a blocker for libification. Global heap-allocated\nstrings introduce thread-safety issues in a multi-repo environment.\n\nMove 'askpass_program' into 'struct repo_config_values' to eliminate\nthis global state. The memory is now safely managed and freed via\n'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 6 ++++--\n environment.h | 1 +\n prompt.c      | 3 ++-\n 3 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex a1204fdcb2..3782bf68aa 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -462,8 +462,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.askpass\")) {\n-\t\tFREE_AND_NULL(askpass_program);\n-\t\treturn git_config_string(&askpass_program, var, value);\n+\t\tFREE_AND_NULL(cfg->askpass_program);\n+\t\treturn git_config_string(&cfg->askpass_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n@@ -724,6 +724,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n+\tcfg->askpass_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -756,4 +757,5 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n+\tFREE_AND_NULL(cfg->askpass_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 22f6697c52..d55b1ba073 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -93,6 +93,7 @@ struct repo_config_values {\n \tchar *excludes_file;\n \tchar *editor_program;\n \tchar *pager_program;\n+\tchar *askpass_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/prompt.c b/prompt.c\nindex 706fba2a50..d8d74c7e37 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -3,6 +3,7 @@\n #include \"git-compat-util.h\"\n #include \"parse.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"prompt.h\"\n@@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)\n \n \t\taskpass = getenv(\"GIT_ASKPASS\");\n \t\tif (!askpass)\n-\t\t\taskpass = askpass_program;\n+\t\t\taskpass = repo_config_values(the_repository)->askpass_program;\n \t\tif (!askpass)\n \t\t\taskpass = getenv(\"SSH_ASKPASS\");\n \t\tif (askpass && *askpass)\n-- \n2.43.0\n\n"},{"id":"547502","messageId":"20260708160300.8852-7-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 6/9] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:57Z","receivedAt":"2026-07-08T16:03:34Z","isPatch":true,"body":"The global variables 'apply_default_whitespace' and\n'apply_default_ignorewhitespace' are used to store the default\nwhitespace configuration for 'git apply'. Move these variables\ninto 'struct repo_config_values' to continue the libification\neffort.\n\nDynamically allocated strings fetched via 'repo_config_get_string()'\nare now tracked per-repository and safely freed in\n'repo_config_values_clear()'.\n\nAs part of this transition, update 'git_apply_config()' to accept a\n'struct repository *' argument rather than relying on the\n'the_repository' global.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n apply.c       | 20 ++++++++++++--------\n environment.c |  6 ++++--\n environment.h |  4 ++--\n 3 files changed, 18 insertions(+), 12 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..66db9b7678 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -47,11 +47,13 @@ struct gitdiff_data {\n \tint p_value;\n };\n \n-static void git_apply_config(void)\n+static void git_apply_config(struct repository *repo)\n {\n-\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n-\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n-\trepo_config(the_repository, git_xmerge_config, NULL);\n+\trepo_config_get_string(repo, \"apply.whitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_whitespace);\n+\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_ignorewhitespace);\n+\trepo_config(repo, git_xmerge_config, NULL);\n }\n \n static int parse_whitespace_option(struct apply_state *state, const char *option)\n@@ -126,10 +128,12 @@ int init_apply_state(struct apply_state *state,\n \tstrset_init(&state->kept_symlinks);\n \tstrbuf_init(&state->root, 0);\n \n-\tgit_apply_config();\n-\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n+\tgit_apply_config(repo);\n+\tif (repo_config_values(repo)->apply_default_whitespace &&\n+\t    parse_whitespace_option(state, repo_config_values(repo)->apply_default_whitespace))\n \t\treturn -1;\n-\tif (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))\n+\tif (repo_config_values(repo)->apply_default_ignorewhitespace &&\n+\t    parse_ignorewhitespace_option(state, repo_config_values(repo)->apply_default_ignorewhitespace))\n \t\treturn -1;\n \treturn 0;\n }\n@@ -192,7 +196,7 @@ int check_apply_state(struct apply_state *state, int force_apply)\n \n static void set_default_whitespace_mode(struct apply_state *state)\n {\n-\tif (!state->whitespace_option && !apply_default_whitespace)\n+\tif (!state->whitespace_option && !repo_config_values(state->repo)->apply_default_whitespace)\n \t\tstate->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);\n }\n \ndiff --git a/environment.c b/environment.c\nindex 3782bf68aa..8744790219 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,8 +49,6 @@ int assume_unchanged;\n int is_bare_repository_cfg = -1; /* unspecified */\n char *git_commit_encoding;\n char *git_log_output_encoding;\n-char *apply_default_whitespace;\n-char *apply_default_ignorewhitespace;\n int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n@@ -725,6 +723,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n \tcfg->askpass_program = NULL;\n+\tcfg->apply_default_whitespace = NULL;\n+\tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -758,4 +758,6 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n \tFREE_AND_NULL(cfg->askpass_program);\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n }\ndiff --git a/environment.h b/environment.h\nindex d55b1ba073..9aecd64152 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -94,6 +94,8 @@ struct repo_config_values {\n \tchar *editor_program;\n \tchar *pager_program;\n \tchar *askpass_program;\n+\tchar *apply_default_whitespace;\n+\tchar *apply_default_ignorewhitespace;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -182,8 +184,6 @@ extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\n-extern char *apply_default_whitespace;\n-extern char *apply_default_ignorewhitespace;\n extern unsigned long pack_size_limit_cfg;\n \n extern int protect_hfs;\n-- \n2.43.0\n\n"},{"id":"547503","messageId":"20260708160300.8852-8-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 7/9] environment: move push_default into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:58Z","receivedAt":"2026-07-08T16:03:36Z","isPatch":true,"body":"The global variable 'push_default' specifies the default behavior of\n'git push' when no explicit refspec is provided. Move 'push_default'\ninto 'struct repo_config_values' to continue the libification effort.\n\nWhile 'enum push_default_type' ideally belongs in 'remote.h', moving it\nthere introduces a circular dependency chain:\n\n  remote.h -> hash.h -> repository.h -> environment.h.\n\nTherefore, the enum definition is kept in 'environment.h' just above\n'struct repo_config_values' with a NEEDSWORK comment for future cleanup.\n\nModify the configuration parsing in environment.c to update the\nper-repository structure directly, and update caller across the\ncodebase to access the value via 'repo_config_values()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n builtin/push.c |  8 ++++----\n environment.c  | 16 +++++++++-------\n environment.h  | 26 ++++++++++++++++----------\n remote.c       |  2 +-\n 4 files changed, 30 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..6dc3224b60 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -88,7 +88,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,\n \t\t}\n \t}\n \n-\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n+\tif (repo_config_values(the_repository)->push_default == PUSH_DEFAULT_UPSTREAM &&\n \t    skip_prefix(matched->name, \"refs/heads/\", &branch_name)) {\n \t\tstruct branch *branch = branch_get(branch_name);\n \t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n@@ -160,7 +160,7 @@ static NORETURN void die_push_simple(struct branch *branch,\n \t * Don't show advice for people who explicitly set\n \t * push.default.\n \t */\n-\tif (push_default == PUSH_DEFAULT_UNSPECIFIED)\n+\tif (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)\n \t\tadvice_pushdefault_maybe = _(\"\\n\"\n \t\t\t\t \"To choose either option permanently, \"\n \t\t\t\t \"see push.default in 'git help config'.\\n\");\n@@ -232,7 +232,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tconst char *dst;\n \tint same_remote;\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tcase PUSH_DEFAULT_MATCHING:\n \t\trefspec_append(&rs, \":\");\n \t\treturn;\n@@ -252,7 +252,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tdst = branch->refname;\n \tsame_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tdefault:\n \tcase PUSH_DEFAULT_UNSPECIFIED:\n \tcase PUSH_DEFAULT_SIMPLE:\ndiff --git a/environment.c b/environment.c\nindex 8744790219..09de2fee87 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -59,7 +59,6 @@ enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n-enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -619,21 +618,23 @@ static int git_default_branch_config(const char *var, const char *value)\n \n static int git_default_push_config(const char *var, const char *value)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tif (!strcmp(var, \"push.default\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"nothing\"))\n-\t\t\tpush_default = PUSH_DEFAULT_NOTHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_NOTHING;\n \t\telse if (!strcmp(value, \"matching\"))\n-\t\t\tpush_default = PUSH_DEFAULT_MATCHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_MATCHING;\n \t\telse if (!strcmp(value, \"simple\"))\n-\t\t\tpush_default = PUSH_DEFAULT_SIMPLE;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_SIMPLE;\n \t\telse if (!strcmp(value, \"upstream\"))\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"tracking\")) /* deprecated */\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"current\"))\n-\t\t\tpush_default = PUSH_DEFAULT_CURRENT;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_CURRENT;\n \t\telse {\n \t\t\terror(_(\"malformed value for %s: %s\"), var, value);\n \t\t\treturn error(_(\"must be one of nothing, matching, simple, \"\n@@ -725,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->askpass_program = NULL;\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n+\tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 9aecd64152..72859b5d76 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -87,6 +87,21 @@ extern const char * const local_repo_env[];\n struct strvec;\n \n struct repository;\n+\n+/*\n+ * NEEDSWORK: It would be better if these definitions could be moved to\n+ * other more specific files, but care is needed to avoid circular\n+ * inclusion issues.\n+ */\n+enum push_default_type {\n+\tPUSH_DEFAULT_NOTHING = 0,\n+\tPUSH_DEFAULT_MATCHING,\n+\tPUSH_DEFAULT_SIMPLE,\n+\tPUSH_DEFAULT_UPSTREAM,\n+\tPUSH_DEFAULT_CURRENT,\n+\tPUSH_DEFAULT_UNSPECIFIED\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -96,6 +111,7 @@ struct repo_config_values {\n \tchar *askpass_program;\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n+\tenum push_default_type push_default;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -197,16 +213,6 @@ enum rebase_setup_type {\n };\n extern enum rebase_setup_type autorebase;\n \n-enum push_default_type {\n-\tPUSH_DEFAULT_NOTHING = 0,\n-\tPUSH_DEFAULT_MATCHING,\n-\tPUSH_DEFAULT_SIMPLE,\n-\tPUSH_DEFAULT_UPSTREAM,\n-\tPUSH_DEFAULT_CURRENT,\n-\tPUSH_DEFAULT_UNSPECIFIED\n-};\n-extern enum push_default_type push_default;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..d48c01d375 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo,\n \tif (remote->mirror)\n \t\treturn tracking_for_push_dest(remote, branch->refname, err);\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(repo)->push_default) {\n \tcase PUSH_DEFAULT_NOTHING:\n \t\treturn error_buf(err, _(\"push has no destination (push.default is 'nothing')\"));\n \n-- \n2.43.0\n\n"},{"id":"547504","messageId":"20260708160300.8852-9-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 8/9] environment: move autorebase into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:02:59Z","receivedAt":"2026-07-08T16:03:40Z","isPatch":true,"body":"The global variable 'autorebase' dictates whether a newly created\nbranch should be configured to automatically rebase by default.\nMove it into 'struct repo_config_values' to continue the\nlibification effort.\n\nThe 'enum rebase_setup_type' definition is moved higher up in\n'environment.h' so that it is visible to the repository-specific\nstructure. The default state AUTOREBASE_NEVER is now correctly\ninitialized in 'repo_config_values_init()'.\n\nConfiguration parsing in 'git_default_branch_config()' is updated to\nwrite directly to the repository's configuration instance.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n branch.c      |  2 +-\n environment.c | 10 +++++-----\n environment.h | 16 ++++++++--------\n 3 files changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 243db7d0fc..e1c1f8c89d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n static int should_setup_rebase(const char *origin)\n {\n-\tswitch (autorebase) {\n+\tswitch (repo_config_values(the_repository)->autorebase) {\n \tcase AUTOREBASE_NEVER:\n \t\treturn 0;\n \tcase AUTOREBASE_LOCAL:\ndiff --git a/environment.c b/environment.c\nindex 09de2fee87..7701aa3bc0 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -58,7 +58,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n-enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -600,13 +599,13 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"never\"))\n-\t\t\tautorebase = AUTOREBASE_NEVER;\n+\t\t\tcfg->autorebase = AUTOREBASE_NEVER;\n \t\telse if (!strcmp(value, \"local\"))\n-\t\t\tautorebase = AUTOREBASE_LOCAL;\n+\t\t\tcfg->autorebase = AUTOREBASE_LOCAL;\n \t\telse if (!strcmp(value, \"remote\"))\n-\t\t\tautorebase = AUTOREBASE_REMOTE;\n+\t\t\tcfg->autorebase = AUTOREBASE_REMOTE;\n \t\telse if (!strcmp(value, \"always\"))\n-\t\t\tautorebase = AUTOREBASE_ALWAYS;\n+\t\t\tcfg->autorebase = AUTOREBASE_ALWAYS;\n \t\telse\n \t\t\treturn error(_(\"malformed value for %s\"), var);\n \t\treturn 0;\n@@ -727,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n+\tcfg->autorebase = AUTOREBASE_NEVER;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 72859b5d76..464ff73136 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -102,6 +102,13 @@ enum push_default_type {\n \tPUSH_DEFAULT_UNSPECIFIED\n };\n \n+enum rebase_setup_type {\n+\tAUTOREBASE_NEVER = 0,\n+\tAUTOREBASE_LOCAL,\n+\tAUTOREBASE_REMOTE,\n+\tAUTOREBASE_ALWAYS\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -112,6 +119,7 @@ struct repo_config_values {\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n+\tenum rebase_setup_type autorebase;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -205,14 +213,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum rebase_setup_type {\n-\tAUTOREBASE_NEVER = 0,\n-\tAUTOREBASE_LOCAL,\n-\tAUTOREBASE_REMOTE,\n-\tAUTOREBASE_ALWAYS\n-};\n-extern enum rebase_setup_type autorebase;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\n-- \n2.43.0\n\n"},{"id":"547505","messageId":"20260708160300.8852-10-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v8 9/9] environment: move object_creation_mode into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-08T16:03:00Z","receivedAt":"2026-07-08T16:03:46Z","isPatch":true,"body":"The global variable 'object_creation_mode' controls how Git creates\nobject files, specifically determining whether to use hardlinks or\nrenames when moving temporary files into the object database. Move\nit into 'struct repo_config_values' to continue the libification\neffort.\n\nMove the 'enum object_creation_mode' definition higher up in\n'environment.h' to ensure it is visible to the structure. Initialize\nthe per-repository value to its default macro value\nOBJECT_CREATION_MODE inside 'repo_config_values_init()'.\n\nUpdate configuration parsing in 'git_default_core_config()' to write\ndirectly to the repository-specific configuration structure.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  6 +++---\n environment.h | 12 ++++++------\n object-file.c |  2 +-\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 7701aa3bc0..e50beda918 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -61,7 +61,6 @@ char *check_roundtrip_encoding;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n-enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;\n int grafts_keep_true_parents;\n unsigned long pack_size_limit_cfg;\n \n@@ -511,9 +510,9 @@ int git_default_core_config(const char *var, const char *value,\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\tif (!strcmp(value, \"rename\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_RENAMES;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES;\n \t\telse if (!strcmp(value, \"link\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n \t\telse\n \t\t\tdie(_(\"invalid mode for object creation: %s\"), value);\n \t\treturn 0;\n@@ -727,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->autorebase = AUTOREBASE_NEVER;\n+\tcfg->object_creation_mode = OBJECT_CREATION_MODE;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 464ff73136..eaa0aba7bc 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -109,6 +109,11 @@ enum rebase_setup_type {\n \tAUTOREBASE_ALWAYS\n };\n \n+enum object_creation_mode {\n+\tOBJECT_CREATION_USES_HARDLINKS = 0,\n+\tOBJECT_CREATION_USES_RENAMES = 1\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -120,6 +125,7 @@ struct repo_config_values {\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n \tenum rebase_setup_type autorebase;\n+\tenum object_creation_mode object_creation_mode;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -213,12 +219,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum object_creation_mode {\n-\tOBJECT_CREATION_USES_HARDLINKS = 0,\n-\tOBJECT_CREATION_USES_RENAMES = 1\n-};\n-extern enum object_creation_mode object_creation_mode;\n-\n extern int grafts_keep_true_parents;\n \n const char *get_log_output_encoding(void);\ndiff --git a/object-file.c b/object-file.c\nindex 9afa842da2..cbbfc8f1dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -415,7 +415,7 @@ int finalize_object_file_flags(struct repository *repo,\n retry:\n \tret = 0;\n \n-\tif (object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n+\tif (repo_config_values(repo)->object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n \t\tgoto try_rename;\n \telse if (link(tmpfile, filename))\n \t\tret = errno;\n-- \n2.43.0\n\n"},{"id":"547561","messageId":"xmqqy0fkq0nw.fsf@gitster.g","threadId":"65873","inReplyTo":"20260708160300.8852-5-cat@malon.dev","subject":"Re: [PATCH v8 4/9] environment: move pager_program into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-09T03:53:07Z","receivedAt":"2026-07-09T03:53:09Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> On top of that, fix a memory leak in pager.c while we are at it.\n\nHmph.\n\n> @@ -75,10 +76,12 @@ static void wait_for_pager_signal(int signo)\n>  \n>  static int core_pager_config(const char *var, const char *value,\n>  \t\t\t     const struct config_context *ctx UNUSED,\n> -\t\t\t     void *data UNUSED)\n> +\t\t\t     void *data)\n>  {\n> +\tstruct repository *r = data;\n> +\n>  \tif (!strcmp(var, \"core.pager\"))\n> -\t\treturn git_config_string(&pager_program, var, value);\n> +\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n\nIsn't this still overwriting what was in the .pager_program member\nof the config values struct?  In check_pager_config() below, there\nis a free() to avoid such a leak, but wouldn't this have the same\nissue?\n\n> @@ -91,10 +94,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n>  \n>  \tpager = getenv(\"GIT_PAGER\");\n>  \tif (!pager) {\n> -\t\tif (!pager_program)\n> +\t\tif (!repo_config_values(r)->pager_program)\n>  \t\t\tread_early_config(r,\n> -\t\t\t\t\t  core_pager_config, NULL);\n> -\t\tpager = pager_program;\n> +\t\t\t\t\t  core_pager_config, r);\n> +\t\tpager = repo_config_values(r)->pager_program;\n>  \t}\n>  \tif (!pager)\n>  \t\tpager = getenv(\"PAGER\");\n> @@ -302,7 +305,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n>  \n>  \tread_early_config(r, pager_command_config, &data);\n>  \n> -\tif (data.value)\n> -\t\tpager_program = data.value;\n> +\tif (data.value) {\n> +\t\tfree(repo_config_values(r)->pager_program);\n> +\t\trepo_config_values(r)->pager_program = data.value;\n> +\t}\n>  \treturn data.want;\n>  }\n"},{"id":"547615","messageId":"20260709161145.13349-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260708160300.8852-1-cat@malon.dev","subject":"[PATCH v9 0/9] migrate more variables into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:36Z","receivedAt":"2026-07-09T16:12:00Z","isPatch":true,"body":"Hi everyone,\n\nThis patch series continues the ongoing libification effort by migrating\na batch of global configuration variables into struct repo_config_values.\n\nWhat does this series do:\n\ninfrastructure & strings (commits 1-6):\nIntroduce 'repo_config_values_clear()' to manage the lifecycle\nof heap-allocated configuration strings. This infrastructure is utilized\nto migrate string variables, including 'excludes_file', 'apply' whitespace\nconfigs, and external programs including 'editor', 'pager', 'askpass'.\n\nenums (commits 7-9):\nMigrate enumerations 'push_default', 'autorebase', and\n'object_creation_mode'. Care was taken to make these types available\nto the configuration structure without triggering circular header\ndependencies.\n\nRFC:\n\nCommit 3~5. Is it really necessary to migrate _program variables?\nhttps://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n\nCommit 6~9. Previous related discussions on 'git_branch_track'.\nhttps://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n\nNote:\n\nSince a new getter 'repo_excludes_file()' is introduced, as previously\npromised, once it is finally merged into 'master', there will be a patch to\nupdate and squash the comments.\n\nSimilarly, I've noticed that the classification and sorting of variables in\n'repo_config_values' don't seem to be correct. There will also be a patch\nto fix this, and I think it will form a commit series along with the comment\npatch?\n\nChange since v8:\n\nFixed a memory leak in pager.c.\n\nThanks!\n\nTian Yuchen (9):\n  repository: introduce repo_config_values_clear()\n  environment: move excludes_file into repo_config_values\n  environment: move editor_program into repo_config_values\n  environment: move pager_program into repo_config_values\n  environment: move askpass_program into repo_config_values\n  environment: migrate apply_default_whitespace and\n    apply_default_ignorewhitespace\n  environment: move push_default into repo_config_values\n  environment: move autorebase into repo_config_values\n  environment: move object_creation_mode into repo_config_values\n\n apply.c        | 20 +++++++-----\n branch.c       |  2 +-\n builtin/push.c |  8 ++---\n dir.c          |  4 +--\n editor.c       |  4 +--\n environment.c  | 87 +++++++++++++++++++++++++++++++++++---------------\n environment.h  | 75 +++++++++++++++++++++++++++----------------\n object-file.c  |  2 +-\n pager.c        | 26 +++++++++------\n prompt.c       |  3 +-\n remote.c       |  2 +-\n repository.c   |  1 +\n 12 files changed, 152 insertions(+), 82 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"547616","messageId":"20260709161145.13349-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 1/9] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:37Z","receivedAt":"2026-07-09T16:12:08Z","isPatch":true,"body":"As part of the ongoing libification effort, dynamically allocated\nglobal configuration variables are being moved into\n'struct repo_config_values'. To prevent memory leaks, we need a\ndestructor to free these heap-allocated variables when a repository\ninstance is torn down.\n\nIntroduce 'repo_config_values_clear()' in environment.c and invoke it\nfrom 'repo_clear()' in repository.c. As a starting point, update this\nnew function to handle the cleanup of 'attributes_file'.\n\nNote:\n\nSubmodules are currently not supported by repo_config_values(), which\nexplicitly BUG()s out if 'repo != the_repository'. Since repo_clear()\ncleans up all repository instances, we must bypass them to prevent\ncrashing.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 19 +++++++++++++++++++\n environment.h |  9 +++++++++\n repository.c  |  1 +\n 3 files changed, 29 insertions(+)\n\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..13677484de 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -726,3 +726,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg;\n+\n+\t/*\n+\t * NEEDSWORK: Submodules are currently not supported by\n+\t * repo_config_values(), which explicitly BUG()s out if\n+\t * repo != the_repository. Since repo_clear() cleans up all\n+\t * repository instances, we must bypass them here to prevent\n+\t * crashing.\n+\t */\n+\tif (repo != the_repository)\n+\t\treturn;\n+\n+\tcfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->attributes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..c4a6a45704 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n \n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repository *repo);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..b31f1b7852 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(repo);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"547617","messageId":"20260709161145.13349-3-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 2/9] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:38Z","receivedAt":"2026-07-09T16:12:11Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL,\n'setup_standard_excludes()'\nin 'dir.c' forcefully evaluates and assigns the XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. It is safely freed using the newly\nintroduced `repo_config_values_clear()` function when the repository\nis torn down.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 15 ++++++++++++---\n environment.h |  4 +++-\n 3 files changed, 17 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex 13677484de..5950592d63 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,14 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tif (!repo_config_values(repo)->excludes_file)\n+\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn repo_config_values(repo)->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +468,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +722,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -744,4 +752,5 @@ void repo_config_values_clear(struct repository *repo)\n \tcfg = repo_config_values(repo);\n \n \tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n }\ndiff --git a/environment.h b/environment.h\nindex c4a6a45704..2e8352de7f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\n-- \n2.43.0\n\n"},{"id":"547618","messageId":"20260709161145.13349-4-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 3/9] environment: move editor_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:39Z","receivedAt":"2026-07-09T16:12:15Z","isPatch":true,"body":"The global variable 'editor_program' holds the path to the user's\npreferred editor. Move 'editor_program' into\n'struct repo_config_values' to continue the libification effort.\n\nThere have been discussions on whether external programs like\neditors truly need to be configured on a per-repository basis within\nthe same process. While a single process might rarely invoke\ndifferent editors, this migration is necessary for two reasons:\n\n1. Developers frequently use different toolchains for different\n   projects. Per-repo configuration respects this.\n\n2. Moving this string into 'repo_config_values' eliminates mutable\n   global state. As the codebase moves toward becoming a long-running\n   processes managing multiple repositories concurrently must\n   not overwrite each other's program configurations.\n\nNo standalone getter function is introduced. Callers directly access\nthe field via 'repo_config_values()'. Heap memory is safely reclaimed\nin 'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n editor.c      | 4 ++--\n environment.c | 7 ++++---\n environment.h | 2 +-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/editor.c b/editor.c\nindex fd174e6a03..07d264cba0 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -29,8 +29,8 @@ const char *git_editor(void)\n \tconst char *editor = getenv(\"GIT_EDITOR\");\n \tint terminal_is_dumb = is_terminal_dumb();\n \n-\tif (!editor && editor_program)\n-\t\teditor = editor_program;\n+\tif (!editor && repo_config_values(the_repository)->editor_program)\n+\t\teditor = repo_config_values(the_repository)->editor_program;\n \tif (!editor && !terminal_is_dumb)\n \t\teditor = getenv(\"VISUAL\");\n \tif (!editor)\ndiff --git a/environment.c b/environment.c\nindex 5950592d63..0a01f4761a 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *editor_program;\n char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n@@ -435,8 +434,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.editor\")) {\n-\t\tFREE_AND_NULL(editor_program);\n-\t\treturn git_config_string(&editor_program, var, value);\n+\t\tFREE_AND_NULL(cfg->editor_program);\n+\t\treturn git_config_string(&cfg->editor_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n@@ -723,6 +722,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n+\tcfg->editor_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -753,4 +753,5 @@ void repo_config_values_clear(struct repository *repo)\n \n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n+\tFREE_AND_NULL(cfg->editor_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 2e8352de7f..1ec19149cb 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -91,6 +91,7 @@ struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n \tchar *excludes_file;\n+\tchar *editor_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *editor_program;\n extern char *askpass_program;\n \n /*\n-- \n2.43.0\n\n"},{"id":"547619","messageId":"20260709161145.13349-5-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 4/9] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:40Z","receivedAt":"2026-07-09T16:12:20Z","isPatch":true,"body":"The 'pager_program' variable is currently defined as a file-scoped\nstatic string in pager.c. Move it into 'struct repo_config_values'.\n\nThe configuration parsing logic remains strictly within pager.c to\nrespect subsystem boundaries. The read/write operations are simply\nredirected to the repository-specific structure using\n'repo_config_values()'.\n\nSimilar to the recent editor_program migration, no standalone getter\nis introduced to keep the code minimal. The dynamically allocated\nmemory is now managed by 'repo_config_values_clear()'.\n\nOn top of that, fix a memory leak in pager.c while we are at it.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  2 ++\n environment.h |  1 +\n pager.c       | 26 +++++++++++++++++---------\n 3 files changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 0a01f4761a..a1204fdcb2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -723,6 +723,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n+\tcfg->pager_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -754,4 +755,5 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n+\tFREE_AND_NULL(cfg->pager_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 1ec19149cb..22f6697c52 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tchar *excludes_file;\n \tchar *editor_program;\n+\tchar *pager_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/pager.c b/pager.c\nindex 35b210e048..bc55546670 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -5,6 +5,8 @@\n #include \"run-command.h\"\n #include \"sigchain.h\"\n #include \"alias.h\"\n+#include \"repository.h\"\n+#include \"environment.h\"\n \n int pager_use_color = 1;\n \n@@ -13,7 +15,6 @@ int pager_use_color = 1;\n #endif\n \n static struct child_process pager_process;\n-static char *pager_program;\n static int old_fd1 = -1, old_fd2 = -1;\n \n /* Is the value coming back from term_columns() just a guess? */\n@@ -75,10 +76,15 @@ static void wait_for_pager_signal(int signo)\n \n static int core_pager_config(const char *var, const char *value,\n \t\t\t     const struct config_context *ctx UNUSED,\n-\t\t\t     void *data UNUSED)\n+\t\t\t     void *data)\n {\n-\tif (!strcmp(var, \"core.pager\"))\n-\t\treturn git_config_string(&pager_program, var, value);\n+\tstruct repository *r = data;\n+\n+\tif (!strcmp(var, \"core.pager\")) {\n+\t\tFREE_AND_NULL(repo_config_values(r)->pager_program);\n+\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n+\t}\n+\n \treturn 0;\n }\n \n@@ -91,10 +97,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n \n \tpager = getenv(\"GIT_PAGER\");\n \tif (!pager) {\n-\t\tif (!pager_program)\n+\t\tif (!repo_config_values(r)->pager_program)\n \t\t\tread_early_config(r,\n-\t\t\t\t\t  core_pager_config, NULL);\n-\t\tpager = pager_program;\n+\t\t\t\t\t  core_pager_config, r);\n+\t\tpager = repo_config_values(r)->pager_program;\n \t}\n \tif (!pager)\n \t\tpager = getenv(\"PAGER\");\n@@ -302,7 +308,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n \n \tread_early_config(r, pager_command_config, &data);\n \n-\tif (data.value)\n-\t\tpager_program = data.value;\n+\tif (data.value) {\n+\t\tfree(repo_config_values(r)->pager_program);\n+\t\trepo_config_values(r)->pager_program = data.value;\n+\t}\n \treturn data.want;\n }\n-- \n2.43.0\n\n"},{"id":"547620","messageId":"20260709161145.13349-6-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 5/9] environment: move askpass_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:41Z","receivedAt":"2026-07-09T16:12:24Z","isPatch":true,"body":"The global variable 'askpass_program' stores the path to the program\nused to prompt the user for credentials. Move it into repo_config_values\nto continue the libification effort.\n\nWhile it is uncommon for a single process to require different askpass\nprograms for different repositories, maintaining this value as a mutable\nglobal string is a blocker for libification. Global heap-allocated\nstrings introduce thread-safety issues in a multi-repo environment.\n\nMove 'askpass_program' into 'struct repo_config_values' to eliminate\nthis global state. The memory is now safely managed and freed via\n'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 6 ++++--\n environment.h | 1 +\n prompt.c      | 3 ++-\n 3 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex a1204fdcb2..3782bf68aa 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -462,8 +462,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.askpass\")) {\n-\t\tFREE_AND_NULL(askpass_program);\n-\t\treturn git_config_string(&askpass_program, var, value);\n+\t\tFREE_AND_NULL(cfg->askpass_program);\n+\t\treturn git_config_string(&cfg->askpass_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n@@ -724,6 +724,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n+\tcfg->askpass_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -756,4 +757,5 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n+\tFREE_AND_NULL(cfg->askpass_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 22f6697c52..d55b1ba073 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -93,6 +93,7 @@ struct repo_config_values {\n \tchar *excludes_file;\n \tchar *editor_program;\n \tchar *pager_program;\n+\tchar *askpass_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/prompt.c b/prompt.c\nindex 706fba2a50..d8d74c7e37 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -3,6 +3,7 @@\n #include \"git-compat-util.h\"\n #include \"parse.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"prompt.h\"\n@@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)\n \n \t\taskpass = getenv(\"GIT_ASKPASS\");\n \t\tif (!askpass)\n-\t\t\taskpass = askpass_program;\n+\t\t\taskpass = repo_config_values(the_repository)->askpass_program;\n \t\tif (!askpass)\n \t\t\taskpass = getenv(\"SSH_ASKPASS\");\n \t\tif (askpass && *askpass)\n-- \n2.43.0\n\n"},{"id":"547621","messageId":"20260709161145.13349-7-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 6/9] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:42Z","receivedAt":"2026-07-09T16:12:29Z","isPatch":true,"body":"The global variables 'apply_default_whitespace' and\n'apply_default_ignorewhitespace' are used to store the default\nwhitespace configuration for 'git apply'. Move these variables\ninto 'struct repo_config_values' to continue the libification\neffort.\n\nDynamically allocated strings fetched via 'repo_config_get_string()'\nare now tracked per-repository and safely freed in\n'repo_config_values_clear()'.\n\nAs part of this transition, update 'git_apply_config()' to accept a\n'struct repository *' argument rather than relying on the\n'the_repository' global.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n apply.c       | 20 ++++++++++++--------\n environment.c |  6 ++++--\n environment.h |  4 ++--\n 3 files changed, 18 insertions(+), 12 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..66db9b7678 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -47,11 +47,13 @@ struct gitdiff_data {\n \tint p_value;\n };\n \n-static void git_apply_config(void)\n+static void git_apply_config(struct repository *repo)\n {\n-\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n-\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n-\trepo_config(the_repository, git_xmerge_config, NULL);\n+\trepo_config_get_string(repo, \"apply.whitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_whitespace);\n+\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_ignorewhitespace);\n+\trepo_config(repo, git_xmerge_config, NULL);\n }\n \n static int parse_whitespace_option(struct apply_state *state, const char *option)\n@@ -126,10 +128,12 @@ int init_apply_state(struct apply_state *state,\n \tstrset_init(&state->kept_symlinks);\n \tstrbuf_init(&state->root, 0);\n \n-\tgit_apply_config();\n-\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n+\tgit_apply_config(repo);\n+\tif (repo_config_values(repo)->apply_default_whitespace &&\n+\t    parse_whitespace_option(state, repo_config_values(repo)->apply_default_whitespace))\n \t\treturn -1;\n-\tif (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))\n+\tif (repo_config_values(repo)->apply_default_ignorewhitespace &&\n+\t    parse_ignorewhitespace_option(state, repo_config_values(repo)->apply_default_ignorewhitespace))\n \t\treturn -1;\n \treturn 0;\n }\n@@ -192,7 +196,7 @@ int check_apply_state(struct apply_state *state, int force_apply)\n \n static void set_default_whitespace_mode(struct apply_state *state)\n {\n-\tif (!state->whitespace_option && !apply_default_whitespace)\n+\tif (!state->whitespace_option && !repo_config_values(state->repo)->apply_default_whitespace)\n \t\tstate->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);\n }\n \ndiff --git a/environment.c b/environment.c\nindex 3782bf68aa..8744790219 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,8 +49,6 @@ int assume_unchanged;\n int is_bare_repository_cfg = -1; /* unspecified */\n char *git_commit_encoding;\n char *git_log_output_encoding;\n-char *apply_default_whitespace;\n-char *apply_default_ignorewhitespace;\n int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n@@ -725,6 +723,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n \tcfg->askpass_program = NULL;\n+\tcfg->apply_default_whitespace = NULL;\n+\tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -758,4 +758,6 @@ void repo_config_values_clear(struct repository *repo)\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n \tFREE_AND_NULL(cfg->askpass_program);\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n }\ndiff --git a/environment.h b/environment.h\nindex d55b1ba073..9aecd64152 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -94,6 +94,8 @@ struct repo_config_values {\n \tchar *editor_program;\n \tchar *pager_program;\n \tchar *askpass_program;\n+\tchar *apply_default_whitespace;\n+\tchar *apply_default_ignorewhitespace;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -182,8 +184,6 @@ extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\n-extern char *apply_default_whitespace;\n-extern char *apply_default_ignorewhitespace;\n extern unsigned long pack_size_limit_cfg;\n \n extern int protect_hfs;\n-- \n2.43.0\n\n"},{"id":"547622","messageId":"20260709161145.13349-8-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 7/9] environment: move push_default into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:43Z","receivedAt":"2026-07-09T16:12:32Z","isPatch":true,"body":"The global variable 'push_default' specifies the default behavior of\n'git push' when no explicit refspec is provided. Move 'push_default'\ninto 'struct repo_config_values' to continue the libification effort.\n\nWhile 'enum push_default_type' ideally belongs in 'remote.h', moving it\nthere introduces a circular dependency chain:\n\n  remote.h -> hash.h -> repository.h -> environment.h.\n\nTherefore, the enum definition is kept in 'environment.h' just above\n'struct repo_config_values' with a NEEDSWORK comment for future cleanup.\n\nModify the configuration parsing in environment.c to update the\nper-repository structure directly, and update caller across the\ncodebase to access the value via 'repo_config_values()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n builtin/push.c |  8 ++++----\n environment.c  | 16 +++++++++-------\n environment.h  | 26 ++++++++++++++++----------\n remote.c       |  2 +-\n 4 files changed, 30 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..6dc3224b60 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -88,7 +88,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,\n \t\t}\n \t}\n \n-\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n+\tif (repo_config_values(the_repository)->push_default == PUSH_DEFAULT_UPSTREAM &&\n \t    skip_prefix(matched->name, \"refs/heads/\", &branch_name)) {\n \t\tstruct branch *branch = branch_get(branch_name);\n \t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n@@ -160,7 +160,7 @@ static NORETURN void die_push_simple(struct branch *branch,\n \t * Don't show advice for people who explicitly set\n \t * push.default.\n \t */\n-\tif (push_default == PUSH_DEFAULT_UNSPECIFIED)\n+\tif (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)\n \t\tadvice_pushdefault_maybe = _(\"\\n\"\n \t\t\t\t \"To choose either option permanently, \"\n \t\t\t\t \"see push.default in 'git help config'.\\n\");\n@@ -232,7 +232,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tconst char *dst;\n \tint same_remote;\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tcase PUSH_DEFAULT_MATCHING:\n \t\trefspec_append(&rs, \":\");\n \t\treturn;\n@@ -252,7 +252,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tdst = branch->refname;\n \tsame_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tdefault:\n \tcase PUSH_DEFAULT_UNSPECIFIED:\n \tcase PUSH_DEFAULT_SIMPLE:\ndiff --git a/environment.c b/environment.c\nindex 8744790219..09de2fee87 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -59,7 +59,6 @@ enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n-enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -619,21 +618,23 @@ static int git_default_branch_config(const char *var, const char *value)\n \n static int git_default_push_config(const char *var, const char *value)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tif (!strcmp(var, \"push.default\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"nothing\"))\n-\t\t\tpush_default = PUSH_DEFAULT_NOTHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_NOTHING;\n \t\telse if (!strcmp(value, \"matching\"))\n-\t\t\tpush_default = PUSH_DEFAULT_MATCHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_MATCHING;\n \t\telse if (!strcmp(value, \"simple\"))\n-\t\t\tpush_default = PUSH_DEFAULT_SIMPLE;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_SIMPLE;\n \t\telse if (!strcmp(value, \"upstream\"))\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"tracking\")) /* deprecated */\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"current\"))\n-\t\t\tpush_default = PUSH_DEFAULT_CURRENT;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_CURRENT;\n \t\telse {\n \t\t\terror(_(\"malformed value for %s: %s\"), var, value);\n \t\t\treturn error(_(\"must be one of nothing, matching, simple, \"\n@@ -725,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->askpass_program = NULL;\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n+\tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 9aecd64152..72859b5d76 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -87,6 +87,21 @@ extern const char * const local_repo_env[];\n struct strvec;\n \n struct repository;\n+\n+/*\n+ * NEEDSWORK: It would be better if these definitions could be moved to\n+ * other more specific files, but care is needed to avoid circular\n+ * inclusion issues.\n+ */\n+enum push_default_type {\n+\tPUSH_DEFAULT_NOTHING = 0,\n+\tPUSH_DEFAULT_MATCHING,\n+\tPUSH_DEFAULT_SIMPLE,\n+\tPUSH_DEFAULT_UPSTREAM,\n+\tPUSH_DEFAULT_CURRENT,\n+\tPUSH_DEFAULT_UNSPECIFIED\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -96,6 +111,7 @@ struct repo_config_values {\n \tchar *askpass_program;\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n+\tenum push_default_type push_default;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -197,16 +213,6 @@ enum rebase_setup_type {\n };\n extern enum rebase_setup_type autorebase;\n \n-enum push_default_type {\n-\tPUSH_DEFAULT_NOTHING = 0,\n-\tPUSH_DEFAULT_MATCHING,\n-\tPUSH_DEFAULT_SIMPLE,\n-\tPUSH_DEFAULT_UPSTREAM,\n-\tPUSH_DEFAULT_CURRENT,\n-\tPUSH_DEFAULT_UNSPECIFIED\n-};\n-extern enum push_default_type push_default;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..d48c01d375 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo,\n \tif (remote->mirror)\n \t\treturn tracking_for_push_dest(remote, branch->refname, err);\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(repo)->push_default) {\n \tcase PUSH_DEFAULT_NOTHING:\n \t\treturn error_buf(err, _(\"push has no destination (push.default is 'nothing')\"));\n \n-- \n2.43.0\n\n"},{"id":"547623","messageId":"20260709161145.13349-9-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 8/9] environment: move autorebase into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:44Z","receivedAt":"2026-07-09T16:12:37Z","isPatch":true,"body":"The global variable 'autorebase' dictates whether a newly created\nbranch should be configured to automatically rebase by default.\nMove it into 'struct repo_config_values' to continue the\nlibification effort.\n\nThe 'enum rebase_setup_type' definition is moved higher up in\n'environment.h' so that it is visible to the repository-specific\nstructure. The default state AUTOREBASE_NEVER is now correctly\ninitialized in 'repo_config_values_init()'.\n\nConfiguration parsing in 'git_default_branch_config()' is updated to\nwrite directly to the repository's configuration instance.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n branch.c      |  2 +-\n environment.c | 10 +++++-----\n environment.h | 16 ++++++++--------\n 3 files changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 243db7d0fc..e1c1f8c89d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n static int should_setup_rebase(const char *origin)\n {\n-\tswitch (autorebase) {\n+\tswitch (repo_config_values(the_repository)->autorebase) {\n \tcase AUTOREBASE_NEVER:\n \t\treturn 0;\n \tcase AUTOREBASE_LOCAL:\ndiff --git a/environment.c b/environment.c\nindex 09de2fee87..7701aa3bc0 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -58,7 +58,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n-enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -600,13 +599,13 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"never\"))\n-\t\t\tautorebase = AUTOREBASE_NEVER;\n+\t\t\tcfg->autorebase = AUTOREBASE_NEVER;\n \t\telse if (!strcmp(value, \"local\"))\n-\t\t\tautorebase = AUTOREBASE_LOCAL;\n+\t\t\tcfg->autorebase = AUTOREBASE_LOCAL;\n \t\telse if (!strcmp(value, \"remote\"))\n-\t\t\tautorebase = AUTOREBASE_REMOTE;\n+\t\t\tcfg->autorebase = AUTOREBASE_REMOTE;\n \t\telse if (!strcmp(value, \"always\"))\n-\t\t\tautorebase = AUTOREBASE_ALWAYS;\n+\t\t\tcfg->autorebase = AUTOREBASE_ALWAYS;\n \t\telse\n \t\t\treturn error(_(\"malformed value for %s\"), var);\n \t\treturn 0;\n@@ -727,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n+\tcfg->autorebase = AUTOREBASE_NEVER;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 72859b5d76..464ff73136 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -102,6 +102,13 @@ enum push_default_type {\n \tPUSH_DEFAULT_UNSPECIFIED\n };\n \n+enum rebase_setup_type {\n+\tAUTOREBASE_NEVER = 0,\n+\tAUTOREBASE_LOCAL,\n+\tAUTOREBASE_REMOTE,\n+\tAUTOREBASE_ALWAYS\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -112,6 +119,7 @@ struct repo_config_values {\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n+\tenum rebase_setup_type autorebase;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -205,14 +213,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum rebase_setup_type {\n-\tAUTOREBASE_NEVER = 0,\n-\tAUTOREBASE_LOCAL,\n-\tAUTOREBASE_REMOTE,\n-\tAUTOREBASE_ALWAYS\n-};\n-extern enum rebase_setup_type autorebase;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\n-- \n2.43.0\n\n"},{"id":"547624","messageId":"20260709161145.13349-10-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v9 9/9] environment: move object_creation_mode into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:11:45Z","receivedAt":"2026-07-09T16:12:40Z","isPatch":true,"body":"The global variable 'object_creation_mode' controls how Git creates\nobject files, specifically determining whether to use hardlinks or\nrenames when moving temporary files into the object database. Move\nit into 'struct repo_config_values' to continue the libification\neffort.\n\nMove the 'enum object_creation_mode' definition higher up in\n'environment.h' to ensure it is visible to the structure. Initialize\nthe per-repository value to its default macro value\nOBJECT_CREATION_MODE inside 'repo_config_values_init()'.\n\nUpdate configuration parsing in 'git_default_core_config()' to write\ndirectly to the repository-specific configuration structure.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  6 +++---\n environment.h | 12 ++++++------\n object-file.c |  2 +-\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 7701aa3bc0..e50beda918 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -61,7 +61,6 @@ char *check_roundtrip_encoding;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n-enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;\n int grafts_keep_true_parents;\n unsigned long pack_size_limit_cfg;\n \n@@ -511,9 +510,9 @@ int git_default_core_config(const char *var, const char *value,\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\tif (!strcmp(value, \"rename\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_RENAMES;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES;\n \t\telse if (!strcmp(value, \"link\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n \t\telse\n \t\t\tdie(_(\"invalid mode for object creation: %s\"), value);\n \t\treturn 0;\n@@ -727,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->autorebase = AUTOREBASE_NEVER;\n+\tcfg->object_creation_mode = OBJECT_CREATION_MODE;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 464ff73136..eaa0aba7bc 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -109,6 +109,11 @@ enum rebase_setup_type {\n \tAUTOREBASE_ALWAYS\n };\n \n+enum object_creation_mode {\n+\tOBJECT_CREATION_USES_HARDLINKS = 0,\n+\tOBJECT_CREATION_USES_RENAMES = 1\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -120,6 +125,7 @@ struct repo_config_values {\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n \tenum rebase_setup_type autorebase;\n+\tenum object_creation_mode object_creation_mode;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -213,12 +219,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum object_creation_mode {\n-\tOBJECT_CREATION_USES_HARDLINKS = 0,\n-\tOBJECT_CREATION_USES_RENAMES = 1\n-};\n-extern enum object_creation_mode object_creation_mode;\n-\n extern int grafts_keep_true_parents;\n \n const char *get_log_output_encoding(void);\ndiff --git a/object-file.c b/object-file.c\nindex 9afa842da2..cbbfc8f1dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -415,7 +415,7 @@ int finalize_object_file_flags(struct repository *repo,\n retry:\n \tret = 0;\n \n-\tif (object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n+\tif (repo_config_values(repo)->object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n \t\tgoto try_rename;\n \telse if (link(tmpfile, filename))\n \t\tret = errno;\n-- \n2.43.0\n\n"},{"id":"547625","messageId":"0da20189-4e5b-4af0-b504-e99ac16d40af@malon.dev","threadId":"65873","inReplyTo":"xmqqy0fkq0nw.fsf@gitster.g","subject":"Re: [PATCH v8 4/9] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-09T16:12:41Z","receivedAt":"2026-07-09T16:12:50Z","isPatch":true,"body":"On 7/9/26 11:53, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> On top of that, fix a memory leak in pager.c while we are at it.\n> \n> Hmph.\n> \n>> @@ -75,10 +76,12 @@ static void wait_for_pager_signal(int signo)\n>>   \n>>   static int core_pager_config(const char *var, const char *value,\n>>   \t\t\t     const struct config_context *ctx UNUSED,\n>> -\t\t\t     void *data UNUSED)\n>> +\t\t\t     void *data)\n>>   {\n>> +\tstruct repository *r = data;\n>> +\n>>   \tif (!strcmp(var, \"core.pager\"))\n>> -\t\treturn git_config_string(&pager_program, var, value);\n>> +\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n> \n> Isn't this still overwriting what was in the .pager_program member\n> of the config values struct?  In check_pager_config() below, there\n> is a free() to avoid such a leak, but wouldn't this have the same\n> issue?\n> \n>> @@ -91,10 +94,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n>>   \n>>   \tpager = getenv(\"GIT_PAGER\");\n>>   \tif (!pager) {\n>> -\t\tif (!pager_program)\n>> +\t\tif (!repo_config_values(r)->pager_program)\n>>   \t\t\tread_early_config(r,\n>> -\t\t\t\t\t  core_pager_config, NULL);\n>> -\t\tpager = pager_program;\n>> +\t\t\t\t\t  core_pager_config, r);\n>> +\t\tpager = repo_config_values(r)->pager_program;\n>>   \t}\n>>   \tif (!pager)\n>>   \t\tpager = getenv(\"PAGER\");\n>> @@ -302,7 +305,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n>>   \n>>   \tread_early_config(r, pager_command_config, &data);\n>>   \n>> -\tif (data.value)\n>> -\t\tpager_program = data.value;\n>> +\tif (data.value) {\n>> +\t\tfree(repo_config_values(r)->pager_program);\n>> +\t\trepo_config_values(r)->pager_program = data.value;\n>> +\t}\n>>   \treturn data.want;\n>>   }\n\nNice catch, sorry for missing that!\n\nRegards, yuchen\n"},{"id":"547627","messageId":"xmqqpl0wp141.fsf@gitster.g","threadId":"65873","inReplyTo":"0da20189-4e5b-4af0-b504-e99ac16d40af@malon.dev","subject":"Re: [PATCH v8 4/9] environment: move pager_program into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-09T16:41:02Z","receivedAt":"2026-07-09T16:41:04Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n>>>   \tif (!strcmp(var, \"core.pager\"))\n>>> -\t\treturn git_config_string(&pager_program, var, value);\n>>> +\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n>> \n>> Isn't this still overwriting what was in the .pager_program member\n>> of the config values struct?  In check_pager_config() below, there\n>> is a free() to avoid such a leak, but wouldn't this have the same\n>> issue?\n>> \n>>> @@ -91,10 +94,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n>>>   \n>>>   \tpager = getenv(\"GIT_PAGER\");\n>>>   \tif (!pager) {\n>>> -\t\tif (!pager_program)\n>>> +\t\tif (!repo_config_values(r)->pager_program)\n>>>   \t\t\tread_early_config(r,\n>>> -\t\t\t\t\t  core_pager_config, NULL);\n>>> -\t\tpager = pager_program;\n>>> +\t\t\t\t\t  core_pager_config, r);\n>>> +\t\tpager = repo_config_values(r)->pager_program;\n>>>   \t}\n>>>   \tif (!pager)\n>>>   \t\tpager = getenv(\"PAGER\");\n>>> @@ -302,7 +305,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n>>>   \n>>>   \tread_early_config(r, pager_command_config, &data);\n>>>   \n>>> -\tif (data.value)\n>>> -\t\tpager_program = data.value;\n>>> +\tif (data.value) {\n>>> +\t\tfree(repo_config_values(r)->pager_program);\n>>> +\t\trepo_config_values(r)->pager_program = data.value;\n>>> +\t}\n>>>   \treturn data.want;\n>>>   }\n>\n> Nice catch, sorry for missing that!\n\nYou do not have to be or say sorry.  This is a team effort, and I am\nreasonably sure that I did not catch _all_ similar bugs in this\niteration.  So before you send an updated version, please make sure\nthat you just do not fix this one only and be content with it.\nInstead try to see if there are other similar issues and fix them,\ntoo.\n\nThanks.\n"},{"id":"547838","messageId":"DJVUGL8XA0Y0.12LN2COXI5BIY@gmail.com","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"Re: [PATCH v9 0/9] migrate more variables into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-11T15:24:12Z","receivedAt":"2026-07-11T15:24:15Z","isPatch":true,"body":"On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n> Hi everyone,\n>\n> This patch series continues the ongoing libification effort by migrating\n> a batch of global configuration variables into struct repo_config_values.\n>\n> What does this series do:\n>\n> infrastructure & strings (commits 1-6):\n> Introduce 'repo_config_values_clear()' to manage the lifecycle\n> of heap-allocated configuration strings. This infrastructure is utilized\n> to migrate string variables, including 'excludes_file', 'apply' whitespace\n> configs, and external programs including 'editor', 'pager', 'askpass'.\n>\n> enums (commits 7-9):\n> Migrate enumerations 'push_default', 'autorebase', and\n> 'object_creation_mode'. Care was taken to make these types available\n> to the configuration structure without triggering circular header\n> dependencies.\n>\n> RFC:\n>\n> Commit 3~5. Is it really necessary to migrate _program variables?\n> https://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n>\n> Commit 6~9. Previous related discussions on 'git_branch_track'.\n> https://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n>\n> Note:\n>\n> Since a new getter 'repo_excludes_file()' is introduced, as previously\n> promised, once it is finally merged into 'master', there will be a patch to\n> update and squash the comments.\n>\n> Similarly, I've noticed that the classification and sorting of variables in\n> 'repo_config_values' don't seem to be correct. There will also be a patch\n> to fix this, and I think it will form a commit series along with the comment\n> patch?\n>\n> Change since v8:\n>\n> Fixed a memory leak in pager.c.\n>\n> Thanks!\n>\n> Tian Yuchen (9):\n>   repository: introduce repo_config_values_clear()\n>   environment: move excludes_file into repo_config_values\n>   environment: move editor_program into repo_config_values\n>   environment: move pager_program into repo_config_values\n>   environment: move askpass_program into repo_config_values\n>   environment: migrate apply_default_whitespace and\n>     apply_default_ignorewhitespace\n>   environment: move push_default into repo_config_values\n>   environment: move autorebase into repo_config_values\n>   environment: move object_creation_mode into repo_config_values\n>\n>  apply.c        | 20 +++++++-----\n>  branch.c       |  2 +-\n>  builtin/push.c |  8 ++---\n>  dir.c          |  4 +--\n>  editor.c       |  4 +--\n>  environment.c  | 87 +++++++++++++++++++++++++++++++++++---------------\n>  environment.h  | 75 +++++++++++++++++++++++++++----------------\n>  object-file.c  |  2 +-\n>  pager.c        | 26 +++++++++------\n>  prompt.c       |  3 +-\n>  remote.c       |  2 +-\n>  repository.c   |  1 +\n>  12 files changed, 152 insertions(+), 82 deletions(-)\n\nHi!\n\nI missed a base-commit to easily apply this locally, could we\nadd one?\n\nThanks!\nPablo\n"},{"id":"547843","messageId":"a7aaa57d-5250-43a6-9646-d1aa29328964@malon.dev","threadId":"65873","inReplyTo":"DJVUGL8XA0Y0.12LN2COXI5BIY@gmail.com","subject":"Re: [PATCH v9 0/9] migrate more variables into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-11T16:11:22Z","receivedAt":"2026-07-11T16:11:32Z","isPatch":true,"body":"Hi Pablo,\n\nOn 7/11/26 23:24, Pablo Sabater wrote:\n> On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n>> Hi everyone,\n>>\n>> This patch series continues the ongoing libification effort by migrating\n>> a batch of global configuration variables into struct repo_config_values.\n>>\n>> What does this series do:\n>>\n>> infrastructure & strings (commits 1-6):\n>> Introduce 'repo_config_values_clear()' to manage the lifecycle\n>> of heap-allocated configuration strings. This infrastructure is utilized\n>> to migrate string variables, including 'excludes_file', 'apply' whitespace\n>> configs, and external programs including 'editor', 'pager', 'askpass'.\n>>\n>> enums (commits 7-9):\n>> Migrate enumerations 'push_default', 'autorebase', and\n>> 'object_creation_mode'. Care was taken to make these types available\n>> to the configuration structure without triggering circular header\n>> dependencies.\n>>\n>> RFC:\n>>\n>> Commit 3~5. Is it really necessary to migrate _program variables?\n>> https://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n>>\n>> Commit 6~9. Previous related discussions on 'git_branch_track'.\n>> https://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n>>\n>> Note:\n>>\n>> Since a new getter 'repo_excludes_file()' is introduced, as previously\n>> promised, once it is finally merged into 'master', there will be a patch to\n>> update and squash the comments.\n>>\n>> Similarly, I've noticed that the classification and sorting of variables in\n>> 'repo_config_values' don't seem to be correct. There will also be a patch\n>> to fix this, and I think it will form a commit series along with the comment\n>> patch?\n>>\n>> Change since v8:\n>>\n>> Fixed a memory leak in pager.c.\n>>\n>> Thanks!\n>>\n>> Tian Yuchen (9):\n>>    repository: introduce repo_config_values_clear()\n>>    environment: move excludes_file into repo_config_values\n>>    environment: move editor_program into repo_config_values\n>>    environment: move pager_program into repo_config_values\n>>    environment: move askpass_program into repo_config_values\n>>    environment: migrate apply_default_whitespace and\n>>      apply_default_ignorewhitespace\n>>    environment: move push_default into repo_config_values\n>>    environment: move autorebase into repo_config_values\n>>    environment: move object_creation_mode into repo_config_values\n>>\n>>   apply.c        | 20 +++++++-----\n>>   branch.c       |  2 +-\n>>   builtin/push.c |  8 ++---\n>>   dir.c          |  4 +--\n>>   editor.c       |  4 +--\n>>   environment.c  | 87 +++++++++++++++++++++++++++++++++++---------------\n>>   environment.h  | 75 +++++++++++++++++++++++++++----------------\n>>   object-file.c  |  2 +-\n>>   pager.c        | 26 +++++++++------\n>>   prompt.c       |  3 +-\n>>   remote.c       |  2 +-\n>>   repository.c   |  1 +\n>>   12 files changed, 152 insertions(+), 82 deletions(-)\n> \n> Hi!\n> \n> I missed a base-commit to easily apply this locally, could we\n> add one?\n> \n> Thanks!\n> Pablo\n\nThanks for pointing out.\n\nThe base commit is 8d96f09e9245ddf80c1981476fcbac8c4bb4125f.\nI will put it on the cover letter in the next reroll (if any)!\n\nRegards, yuchen\n"},{"id":"547846","messageId":"DJVWYOJNU0IW.1M107L4ABH54V@gmail.com","threadId":"65873","inReplyTo":"20260709161145.13349-2-cat@malon.dev","subject":"Re: [PATCH v9 1/9] repository: introduce repo_config_values_clear()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-11T17:21:52Z","receivedAt":"2026-07-11T17:21:55Z","isPatch":true,"body":"On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n> As part of the ongoing libification effort, dynamically allocated\n> global configuration variables are being moved into\n> 'struct repo_config_values'. To prevent memory leaks, we need a\n> destructor to free these heap-allocated variables when a repository\n> instance is torn down.\n>\n> Introduce 'repo_config_values_clear()' in environment.c and invoke it\n> from 'repo_clear()' in repository.c. As a starting point, update this\n> new function to handle the cleanup of 'attributes_file'.\n\nMakes sense.\n\n>\n> Note:\n>\n> Submodules are currently not supported by repo_config_values(), which\n> explicitly BUG()s out if 'repo != the_repository'. Since repo_clear()\n> cleans up all repository instances, we must bypass them to prevent\n> crashing.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  environment.c | 19 +++++++++++++++++++\n>  environment.h |  9 +++++++++\n>  repository.c  |  1 +\n>  3 files changed, 29 insertions(+)\n>\n> diff --git a/environment.c b/environment.c\n> index ba2c60103f..13677484de 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -726,3 +726,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  \tcfg->sparse_expect_files_outside_of_patterns = 0;\n>  \tcfg->warn_on_object_refname_ambiguity = 1;\n>  }\n> +\n> +void repo_config_values_clear(struct repository *repo)\n> +{\n> +\tstruct repo_config_values *cfg;\n> +\n> +\t/*\n> +\t * NEEDSWORK: Submodules are currently not supported by\n> +\t * repo_config_values(), which explicitly BUG()s out if\n> +\t * repo != the_repository. Since repo_clear() cleans up all\n> +\t * repository instances, we must bypass them here to prevent\n> +\t * crashing.\n> +\t */\n> +\tif (repo != the_repository)\n> +\t\treturn;\n> +\n> +\tcfg = repo_config_values(repo);\n> +\n> +\tFREE_AND_NULL(cfg->attributes_file);\n> +}\n> diff --git a/environment.h b/environment.h\n> index 6f18286955..c4a6a45704 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n>\n>  void repo_config_values_init(struct repo_config_values *cfg);\n>\n> +/*\n> + * Frees memory allocated for dynamically loaded configuration values\n> + * inside `repo_config_values`.\n> + *\n> + * As dynamically allocated variables are migrated into this struct,\n> + * their FREE_AND_NULL() calls should be appended here.\n> + */\n> +void repo_config_values_clear(struct repository *repo);\n\nI think that I'm not comfortable having the _init() and the _clear()\nfunctions with different signatures.\n\n_clear() takes struct repository to dodge a BUG().\n\nI would like to have both signatures equal, why can't we just do directly:\n\n  void repo_config_values_clear(struct repo_config_values *cfg)\n  {\n  \tFREE_AND_NULL(cfg->attributes_file);\n  }\n\nand call from repo_clear():\n\n  repo_config_values_clear(&repo->config_values_private_)\n\nI get that the workaround might be to not access directly to\n&repo->config_values_private_ which repo_config_values() returns but for\nexample initialize_repository() access this _private_ field directly as\nwell.\n\nEven with the NEEDSWORK it is a silent return, what will happen when\nsubmodules are supported? If no one remembers to change it we will leak\nthe submodules silently.\n\nAlso at repo_init(), initialize_repository() sets repo->initialized\nbefore anything can fail and call repo_clear() but\nrepo_config_values_clear() should be able to free attributes_file even\njust after a memset() (which happens before initialize_repository()).\nBut calling repo_config_values has a BUG() in case of\n!repo->initialized are we comfortable with this assert?\n\n> +\n>  /*\n>   * TODO: All the below state either explicitly or implicitly relies on\n>   * `the_repository`. We should eventually get rid of these and make the\n> diff --git a/repository.c b/repository.c\n> index 187dd471c4..b31f1b7852 100644\n> --- a/repository.c\n> +++ b/repository.c\n> @@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n>  \tFREE_AND_NULL(repo->parsed_objects);\n>\n>  \trepo_settings_clear(repo);\n> +\trepo_config_values_clear(repo);\n>\n>  \tif (repo->config) {\n>  \t\tgit_configset_clear(repo->config);\n\nRegards,\nPablo\n"},{"id":"547847","messageId":"DJVY828NHN8O.22CRAQOF73S6D@gmail.com","threadId":"65873","inReplyTo":"20260709161145.13349-3-cat@malon.dev","subject":"Re: [PATCH v9 2/9] environment: move excludes_file into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-11T18:21:08Z","receivedAt":"2026-07-11T18:21:12Z","isPatch":true,"body":"On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n> The global variable 'excludes_file' is used to track the path to the\n> global ignore file. If this variable is NULL,\n> 'setup_standard_excludes()'\n\nNit: Strange line break here.\n\n> in 'dir.c' forcefully evaluates and assigns the XDG default path to it.\n>\n> Continue the libification effort by encapsulating this lazy-loading\n> fallback logic into a proper getter and moving the variable into\n> 'struct repo_config_values'.\n>\n> Since 'excludes_file' is a dynamically allocated string, it requires\n> proper heap memory management. It is safely freed using the newly\n> introduced `repo_config_values_clear()` function when the repository\n> is torn down.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  dir.c         |  4 ++--\n>  environment.c | 15 ++++++++++++---\n>  environment.h |  4 +++-\n>  3 files changed, 17 insertions(+), 6 deletions(-)\n>\n> diff --git a/dir.c b/dir.c\n> index 7a73690fbc..4f87a52b3c 100644\n> --- a/dir.c\n> +++ b/dir.c\n> @@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n>\n>  void setup_standard_excludes(struct dir_struct *dir)\n>  {\n> +\tconst char *excludes_file = repo_excludes_file(the_repository);\n> +\n>  \tdir->exclude_per_dir = \".gitignore\";\n>\n>  \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n> -\tif (!excludes_file)\n> -\t\texcludes_file = xdg_config_home(\"ignore\");\n>  \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n>  \t\tadd_patterns_from_file_1(dir, excludes_file,\n>  \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\n> diff --git a/environment.c b/environment.c\n> index 13677484de..5950592d63 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n>  enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n>  char *editor_program;\n>  char *askpass_program;\n> -char *excludes_file;\n>  enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n>  enum eol core_eol = EOL_UNSET;\n>  int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n> @@ -134,6 +133,14 @@ int is_bare_repository(void)\n>  \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>  }\n>\n> +const char *repo_excludes_file(struct repository *repo)\n> +{\n> +\tif (!repo_config_values(repo)->excludes_file)\n> +\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n> +\n> +\treturn repo_config_values(repo)->excludes_file;\n> +}\n\nrepo_config_values() returns a pointer so there should be no need to\ncall the function 3 times.\n\nWe could have the function be called once and use it then:\n\n  const char *repo_excludes_file(struct repository *repo)\n  {\n\t  struct repo_config_values *cfg = repo_config_values(repo);\n\n\t  if (!cfg->excludes_file)\n\t\t  cfg->excludes_file = xdg_config_home(\"ignore\");\n\n\t  return cfg->excludes_file;\n  }\n\n> +\n>  int have_git_dir(void)\n>  {\n>  \treturn startup_info->have_repository\n> @@ -461,8 +468,8 @@ int git_default_core_config(const char *var, const char *value,\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.excludesfile\")) {\n> -\t\tFREE_AND_NULL(excludes_file);\n> -\t\treturn git_config_pathname(&excludes_file, var, value);\n> +\t\tFREE_AND_NULL(cfg->excludes_file);\n> +\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.whitespace\")) {\n> @@ -715,6 +722,7 @@ int git_default_config(const char *var, const char *value,\n>  void repo_config_values_init(struct repo_config_values *cfg)\n>  {\n>  \tcfg->attributes_file = NULL;\n> +\tcfg->excludes_file = NULL;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> @@ -744,4 +752,5 @@ void repo_config_values_clear(struct repository *repo)\n>  \tcfg = repo_config_values(repo);\n>\n>  \tFREE_AND_NULL(cfg->attributes_file);\n> +\tFREE_AND_NULL(cfg->excludes_file);\n>  }\n> diff --git a/environment.h b/environment.h\n> index c4a6a45704..2e8352de7f 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -90,6 +90,7 @@ struct repository;\n>  struct repo_config_values {\n>  \t/* section \"core\" config values */\n>  \tchar *attributes_file;\n> +\tchar *excludes_file;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n> @@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n>  int git_default_core_config(const char *var, const char *value,\n>  \t\t\t    const struct config_context *ctx, void *cb);\n>\n> +const char *repo_excludes_file(struct repository *repo);\n> +\n>  void repo_config_values_init(struct repo_config_values *cfg);\n>\n>  /*\n> @@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n>\n>  extern char *editor_program;\n>  extern char *askpass_program;\n> -extern char *excludes_file;\n>\n>  /*\n>   * The character that begins a commented line in user-editable file\n\nThe rest looks fine.\n\nRegards,\nPablo\n"},{"id":"547848","messageId":"95f46463-a6e7-4b35-8ff4-ac89cadd6437@malon.dev","threadId":"65873","inReplyTo":"DJVWYOJNU0IW.1M107L4ABH54V@gmail.com","subject":"Re: [PATCH v9 1/9] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-11T18:35:23Z","receivedAt":"2026-07-11T18:35:30Z","isPatch":true,"body":"Hii Pablo,\n\nOn 7/12/26 01:21, Pablo Sabater wrote:\n> On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n>> As part of the ongoing libification effort, dynamically allocated\n>> global configuration variables are being moved into\n>> 'struct repo_config_values'. To prevent memory leaks, we need a\n>> destructor to free these heap-allocated variables when a repository\n>> instance is torn down.\n>>\n>> Introduce 'repo_config_values_clear()' in environment.c and invoke it\n>> from 'repo_clear()' in repository.c. As a starting point, update this\n>> new function to handle the cleanup of 'attributes_file'.\n> \n> Makes sense.\n> \n>>\n>> Note:\n>>\n>> Submodules are currently not supported by repo_config_values(), which\n>> explicitly BUG()s out if 'repo != the_repository'. Since repo_clear()\n>> cleans up all repository instances, we must bypass them to prevent\n>> crashing.\n>>\n>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n>> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n>> Signed-off-by: Tian Yuchen <cat@malon.dev>\n>> ---\n>>   environment.c | 19 +++++++++++++++++++\n>>   environment.h |  9 +++++++++\n>>   repository.c  |  1 +\n>>   3 files changed, 29 insertions(+)\n>>\n>> diff --git a/environment.c b/environment.c\n>> index ba2c60103f..13677484de 100644\n>> --- a/environment.c\n>> +++ b/environment.c\n>> @@ -726,3 +726,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>>   \tcfg->sparse_expect_files_outside_of_patterns = 0;\n>>   \tcfg->warn_on_object_refname_ambiguity = 1;\n>>   }\n>> +\n>> +void repo_config_values_clear(struct repository *repo)\n>> +{\n>> +\tstruct repo_config_values *cfg;\n>> +\n>> +\t/*\n>> +\t * NEEDSWORK: Submodules are currently not supported by\n>> +\t * repo_config_values(), which explicitly BUG()s out if\n>> +\t * repo != the_repository. Since repo_clear() cleans up all\n>> +\t * repository instances, we must bypass them here to prevent\n>> +\t * crashing.\n>> +\t */\n>> +\tif (repo != the_repository)\n>> +\t\treturn;\n>> +\n>> +\tcfg = repo_config_values(repo);\n>> +\n>> +\tFREE_AND_NULL(cfg->attributes_file);\n>> +}\n>> diff --git a/environment.h b/environment.h\n>> index 6f18286955..c4a6a45704 100644\n>> --- a/environment.h\n>> +++ b/environment.h\n>> @@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n>>\n>>   void repo_config_values_init(struct repo_config_values *cfg);\n>>\n>> +/*\n>> + * Frees memory allocated for dynamically loaded configuration values\n>> + * inside `repo_config_values`.\n>> + *\n>> + * As dynamically allocated variables are migrated into this struct,\n>> + * their FREE_AND_NULL() calls should be appended here.\n>> + */\n>> +void repo_config_values_clear(struct repository *repo);\n> \n> I think that I'm not comfortable having the _init() and the _clear()\n> functions with different signatures.\n> \n> _clear() takes struct repository to dodge a BUG().\n> \n> I would like to have both signatures equal, why can't we just do directly:\n> \n>    void repo_config_values_clear(struct repo_config_values *cfg)\n>    {\n>    \tFREE_AND_NULL(cfg->attributes_file);\n>    }\n> \n> and call from repo_clear():\n> \n>    repo_config_values_clear(&repo->config_values_private_)\n> \n\nI particularly agree with your point that the signatures of these two \nfunctions should be consistent. I missed it tbh...I will change it in \nthe next reroll.\n\nHowever, I think it makes more sense to refactor to pass in 'struct \nrepository', which is consistent with repo-settings.\n\n> I get that the workaround might be to not access directly to\n> &repo->config_values_private_ which repo_config_values() returns but for\n> example initialize_repository() access this _private_ field directly as\n> well.\n\nNow that we have used the _private_ suffix, if we can just define a \n_clear() to bypass the assertion of repo_config_values(), wouldn't this \nbe self-deception? I'm not saying that the original lines are \nnecessarily correct... but I do think that semantically speaking, it is \ninappropriate to pass in config_values_private_ to _clear().\n\n> Also at repo_init(), initialize_repository() sets repo->initialized\n> before anything can fail and call repo_clear() but\n> repo_config_values_clear() should be able to free attributes_file even\n> just after a memset() (which happens before initialize_repository()).\n> But calling repo_config_values has a BUG() in case of\n> !repo->initialized are we comfortable with this assert?\n\nThis goes back to the previous topic: Who is responsible for the call to \n_clear()? Who is responsible for filtering all those invalid usage of \nrepo instances? Faced with a repo instance that was not initialized but \nwas handed over to _clear() in some way, we have two concepts:\n\n- It doesn't matter. Since we always handle config_values_private_, it's \nNULL at this point, so we don't BUG() and continue.\n\n- The very existence of such a repo is a mistake. It shouldn't have \nappeared and shouldn't have been passed on to me. However, since this \nsituation is relatively common at this point, we choose to return \ninstead of BUG()ing it directly to temporarily avoid it. We will \ngradually tighten the conditions. When the invalid calls are eliminated \nin the end, such checks will no longer exist.\n\nOur consensus should at least be that this 'use of uninitialized repos' \nis bad, so they are just two different ways to solve unexpected \nsituations. However, in my opinion, the difference between these two \nconcepts lies in whether we are consciously moving the assertion \ndownward. I think the latter line of thinking does this better.\n\n\n >\n > Even with the NEEDSWORK it is a silent return, what will happen when\n > submodules are supported? If no one remembers to change it we will leak\n > the submodules silently.\n >\n\nI will remember to change it ;)\n\nRegards, yuchen\n\n>> +\n>>   /*\n>>    * TODO: All the below state either explicitly or implicitly relies on\n>>    * `the_repository`. We should eventually get rid of these and make the\n>> diff --git a/repository.c b/repository.c\n>> index 187dd471c4..b31f1b7852 100644\n>> --- a/repository.c\n>> +++ b/repository.c\n>> @@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n>>   \tFREE_AND_NULL(repo->parsed_objects);\n>>\n>>   \trepo_settings_clear(repo);\n>> +\trepo_config_values_clear(repo);\n>>\n>>   \tif (repo->config) {\n>>   \t\tgit_configset_clear(repo->config);\n> \n> Regards,\n> Pablo\n\n"},{"id":"547849","messageId":"DJVZ5QPXBFY8.VBJN0TB3WROC@gmail.com","threadId":"65873","inReplyTo":"20260706142530.3681520-4-cat@malon.dev","subject":"Re: [PATCH v7 3/9] environment: move editor_program into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-11T19:05:07Z","receivedAt":"2026-07-11T19:05:10Z","isPatch":true,"body":"On Mon Jul 6, 2026 at 4:25 PM CEST, Tian Yuchen wrote:\n> The global variable 'editor_program' holds the path to the user's\n> preferred editor. Move 'editor_program' into\n> 'struct repo_config_values' to continue the libification effort.\n>\n> There have been discussions on whether external programs like\n> editors truly need to be configured on a per-repository basis within\n> the same process. While a single process might rarely invoke\n> different editors, this migration is necessary for two reasons:\n>\n> 1. Developers frequently use different toolchains for different\n>    projects. Per-repo configuration respects this.\n>\n> 2. Moving this string into 'repo_config_values' eliminates mutable\n>    global state. As the codebase moves toward becoming a long-running\n>    processes managing multiple repositories concurrently must\n>    not overwrite each other's program configurations.\n\nNit:\n\n\"As the codebase moves toward becoming long-running processes, managing\nmultiple repositories concurrently must not overwrite each other's\nprogram configurations\"\n\n>\n> No standalone getter function is introduced. Callers directly access\n> the field via 'repo_config_values()'. Heap memory is safely reclaimed\n> in 'repo_config_values_clear()'.\n\nWhy no getter function here? in patch 2/9 repo_excludes_file() is\nintroduced and also access the field via repo_config_values().\n\nSuper nit: on a previous commit you used backquotes for\nrepo_config_values_clear() but now you're using single quotes.\n\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  editor.c      | 4 ++--\n>  environment.c | 7 ++++---\n>  environment.h | 2 +-\n>  3 files changed, 7 insertions(+), 6 deletions(-)\n>\n> diff --git a/editor.c b/editor.c\n> index fd174e6a03..07d264cba0 100644\n> --- a/editor.c\n> +++ b/editor.c\n> @@ -29,8 +29,8 @@ const char *git_editor(void)\n>  \tconst char *editor = getenv(\"GIT_EDITOR\");\n>  \tint terminal_is_dumb = is_terminal_dumb();\n>\n> -\tif (!editor && editor_program)\n> -\t\teditor = editor_program;\n> +\tif (!editor && repo_config_values(the_repository)->editor_program)\n> +\t\teditor = repo_config_values(the_repository)->editor_program;\n\nSame as the previous patch, we can store repo_config_values() and avoid\nre-calling the function.\nAlso, do we need the right-side condition?\n\n>  \tif (!editor && !terminal_is_dumb)\n>  \t\teditor = getenv(\"VISUAL\");\n>  \tif (!editor)\n> diff --git a/environment.c b/environment.c\n> index 5950592d63..0a01f4761a 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -55,7 +55,6 @@ int fsync_object_files = -1;\n>  int use_fsync = -1;\n>  enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n>  enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n> -char *editor_program;\n>  char *askpass_program;\n>  enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n>  enum eol core_eol = EOL_UNSET;\n> @@ -435,8 +434,8 @@ int git_default_core_config(const char *var, const char *value,\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.editor\")) {\n> -\t\tFREE_AND_NULL(editor_program);\n> -\t\treturn git_config_string(&editor_program, var, value);\n> +\t\tFREE_AND_NULL(cfg->editor_program);\n> +\t\treturn git_config_string(&cfg->editor_program, var, value);\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.commentchar\") ||\n> @@ -723,6 +722,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  {\n>  \tcfg->attributes_file = NULL;\n>  \tcfg->excludes_file = NULL;\n> +\tcfg->editor_program = NULL;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> @@ -753,4 +753,5 @@ void repo_config_values_clear(struct repository *repo)\n>\n>  \tFREE_AND_NULL(cfg->attributes_file);\n>  \tFREE_AND_NULL(cfg->excludes_file);\n> +\tFREE_AND_NULL(cfg->editor_program);\n>  }\n> diff --git a/environment.h b/environment.h\n> index 2e8352de7f..1ec19149cb 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -91,6 +91,7 @@ struct repo_config_values {\n>  \t/* section \"core\" config values */\n>  \tchar *attributes_file;\n>  \tchar *excludes_file;\n> +\tchar *editor_program;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n> @@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n>  extern char *git_commit_encoding;\n>  extern char *git_log_output_encoding;\n>\n> -extern char *editor_program;\n>  extern char *askpass_program;\n>\n>  /*\n\nRegards,\nPablo\n"},{"id":"547857","messageId":"DJVZP8E2GS7C.1X325XFFFZ6WR@gmail.com","threadId":"65873","inReplyTo":"20260709161145.13349-4-cat@malon.dev","subject":"Re: [PATCH v9 3/9] environment: move editor_program into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-11T19:30:35Z","receivedAt":"2026-07-11T19:30:38Z","isPatch":true,"body":"[Resending review against v9, I accidentally sent the review in-reply-to\nv7. The patch seems to not have changed and it applies to v9 as well.]\n\nOn Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n> The global variable 'editor_program' holds the path to the user's\n> preferred editor. Move 'editor_program' into\n> 'struct repo_config_values' to continue the libification effort.\n>\n> There have been discussions on whether external programs like\n> editors truly need to be configured on a per-repository basis within\n> the same process. While a single process might rarely invoke\n> different editors, this migration is necessary for two reasons:\n>\n> 1. Developers frequently use different toolchains for different\n>    projects. Per-repo configuration respects this.\n>\n> 2. Moving this string into 'repo_config_values' eliminates mutable\n>    global state. As the codebase moves toward becoming a long-running\n>    processes managing multiple repositories concurrently must\n>    not overwrite each other's program configurations.\n\nNit:\n\"As the codebase moves toward becoming long-running processes, managing\nmultiple repositories concurrently must not overwrite each other's\nprogram configurations\"\n\n>\n> No standalone getter function is introduced. Callers directly access\n> the field via 'repo_config_values()'. Heap memory is safely reclaimed\n> in 'repo_config_values_clear()'.\n\nWhy no getter function here? in patch 2/9 repo_excludes_file() is\nintroduced and also access the field via repo_config_values().\n\nSuper nit: on a previous commit you used backquotes for\nrepo_config_values_clear() but now you're using single quotes.\n\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  editor.c      | 4 ++--\n>  environment.c | 7 ++++---\n>  environment.h | 2 +-\n>  3 files changed, 7 insertions(+), 6 deletions(-)\n>\n> diff --git a/editor.c b/editor.c\n> index fd174e6a03..07d264cba0 100644\n> --- a/editor.c\n> +++ b/editor.c\n> @@ -29,8 +29,8 @@ const char *git_editor(void)\n>  \tconst char *editor = getenv(\"GIT_EDITOR\");\n>  \tint terminal_is_dumb = is_terminal_dumb();\n>\n> -\tif (!editor && editor_program)\n> -\t\teditor = editor_program;\n> +\tif (!editor && repo_config_values(the_repository)->editor_program)\n> +\t\teditor = repo_config_values(the_repository)->editor_program;\n\nSame as the previous patch, we can store repo_config_values() and avoid\nre-calling the function.\nAlso, do we need the right-side condition?\n\n>  \tif (!editor && !terminal_is_dumb)\n>  \t\teditor = getenv(\"VISUAL\");\n>  \tif (!editor)\n> diff --git a/environment.c b/environment.c\n> index 5950592d63..0a01f4761a 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -55,7 +55,6 @@ int fsync_object_files = -1;\n>  int use_fsync = -1;\n>  enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n>  enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n> -char *editor_program;\n>  char *askpass_program;\n>  enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n>  enum eol core_eol = EOL_UNSET;\n> @@ -435,8 +434,8 @@ int git_default_core_config(const char *var, const char *value,\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.editor\")) {\n> -\t\tFREE_AND_NULL(editor_program);\n> -\t\treturn git_config_string(&editor_program, var, value);\n> +\t\tFREE_AND_NULL(cfg->editor_program);\n> +\t\treturn git_config_string(&cfg->editor_program, var, value);\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.commentchar\") ||\n> @@ -723,6 +722,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  {\n>  \tcfg->attributes_file = NULL;\n>  \tcfg->excludes_file = NULL;\n> +\tcfg->editor_program = NULL;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> @@ -753,4 +753,5 @@ void repo_config_values_clear(struct repository *repo)\n>\n>  \tFREE_AND_NULL(cfg->attributes_file);\n>  \tFREE_AND_NULL(cfg->excludes_file);\n> +\tFREE_AND_NULL(cfg->editor_program);\n>  }\n> diff --git a/environment.h b/environment.h\n> index 2e8352de7f..1ec19149cb 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -91,6 +91,7 @@ struct repo_config_values {\n>  \t/* section \"core\" config values */\n>  \tchar *attributes_file;\n>  \tchar *excludes_file;\n> +\tchar *editor_program;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n> @@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n>  extern char *git_commit_encoding;\n>  extern char *git_log_output_encoding;\n>\n> -extern char *editor_program;\n>  extern char *askpass_program;\n>\n>  /*\n\nRegards,\nPablo\n"},{"id":"547859","messageId":"CAN5EUNQpJWt_FD1DvH4qRn12PueNfjXcdqOcEk1eh6dbd_1i2Q@mail.gmail.com","threadId":"65873","inReplyTo":"DJVZ5QPXBFY8.VBJN0TB3WROC@gmail.com","subject":"Re: [PATCH v7 3/9] environment: move editor_program into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-11T19:38:48Z","receivedAt":"2026-07-11T19:39:00Z","isPatch":true,"body":"Please ignore this. I replied to v7 by mistake. I've resent it correctly at:\nhttps://lore.kernel.org/git/DJVZP8E2GS7C.1X325XFFFZ6WR@gmail.com/#t\n\nPablo\n"},{"id":"547864","messageId":"xmqqo6gd9qyr.fsf@gitster.g","threadId":"65873","inReplyTo":"DJVUGL8XA0Y0.12LN2COXI5BIY@gmail.com","subject":"Re: [PATCH v9 0/9] migrate more variables into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-11T21:06:04Z","receivedAt":"2026-07-11T21:06:06Z","isPatch":true,"body":"\"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n\n> On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n>> ...\n> Hi!\n>\n> I missed a base-commit to easily apply this locally, could we\n> add one?\n>\n> Thanks!\n> Pablo\n\nFYI, a topic that is in 'seen' can be extracted from my tree by\ninspecting \"git log --oneline origin/master..origin/seen\" and\nfinding the commit that merges the series.\n\n\n\n"},{"id":"547865","messageId":"xmqqjyr19qrx.fsf@gitster.g","threadId":"65873","inReplyTo":"a7aaa57d-5250-43a6-9646-d1aa29328964@malon.dev","subject":"Re: [PATCH v9 0/9] migrate more variables into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-11T21:10:10Z","receivedAt":"2026-07-11T21:10:13Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n>> I missed a base-commit to easily apply this locally, could we\n>> add one?\n>> \n>> Thanks!\n>> Pablo\n>\n> Thanks for pointing out.\n>\n> The base commit is 8d96f09e9245ddf80c1981476fcbac8c4bb4125f.\n> I will put it on the cover letter in the next reroll (if any)!\n>\n> Regards, yuchen\n\nFWIW, I think I queued it on top of ab776a62a7 (Git 2.55-rc2,\n2026-06-22).\n\nOthers can find it out by looking at the output from\n\n    $ git log --oneline origin/master....origin/seen\n\nand finding the commit that merges the topic.\n\n\n"},{"id":"547881","messageId":"DJWHIWVJ52UW.24D8DAKBNDMLB@gmail.com","threadId":"65873","inReplyTo":"95f46463-a6e7-4b35-8ff4-ac89cadd6437@malon.dev","subject":"Re: [PATCH v9 1/9] repository: introduce repo_config_values_clear()","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-12T09:28:39Z","receivedAt":"2026-07-12T09:28:43Z","isPatch":true,"body":"On Sat Jul 11, 2026 at 8:35 PM CEST, Tian Yuchen wrote:\n> Hii Pablo,\n>\n> On 7/12/26 01:21, Pablo Sabater wrote:\n>> On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n>>> As part of the ongoing libification effort, dynamically allocated\n>>> global configuration variables are being moved into\n>>> 'struct repo_config_values'. To prevent memory leaks, we need a\n>>> destructor to free these heap-allocated variables when a repository\n>>> instance is torn down.\n>>>\n>>> Introduce 'repo_config_values_clear()' in environment.c and invoke it\n>>> from 'repo_clear()' in repository.c. As a starting point, update this\n>>> new function to handle the cleanup of 'attributes_file'.\n>>\n>> Makes sense.\n>>\n>>>\n>>> Note:\n>>>\n>>> Submodules are currently not supported by repo_config_values(), which\n>>> explicitly BUG()s out if 'repo != the_repository'. Since repo_clear()\n>>> cleans up all repository instances, we must bypass them to prevent\n>>> crashing.\n>>>\n>>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>>> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n>>> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n>>> Signed-off-by: Tian Yuchen <cat@malon.dev>\n>>> ---\n>>>   environment.c | 19 +++++++++++++++++++\n>>>   environment.h |  9 +++++++++\n>>>   repository.c  |  1 +\n>>>   3 files changed, 29 insertions(+)\n>>>\n>>> diff --git a/environment.c b/environment.c\n>>> index ba2c60103f..13677484de 100644\n>>> --- a/environment.c\n>>> +++ b/environment.c\n>>> @@ -726,3 +726,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>>>   \tcfg->sparse_expect_files_outside_of_patterns = 0;\n>>>   \tcfg->warn_on_object_refname_ambiguity = 1;\n>>>   }\n>>> +\n>>> +void repo_config_values_clear(struct repository *repo)\n>>> +{\n>>> +\tstruct repo_config_values *cfg;\n>>> +\n>>> +\t/*\n>>> +\t * NEEDSWORK: Submodules are currently not supported by\n>>> +\t * repo_config_values(), which explicitly BUG()s out if\n>>> +\t * repo != the_repository. Since repo_clear() cleans up all\n>>> +\t * repository instances, we must bypass them here to prevent\n>>> +\t * crashing.\n>>> +\t */\n>>> +\tif (repo != the_repository)\n>>> +\t\treturn;\n>>> +\n>>> +\tcfg = repo_config_values(repo);\n>>> +\n>>> +\tFREE_AND_NULL(cfg->attributes_file);\n>>> +}\n>>> diff --git a/environment.h b/environment.h\n>>> index 6f18286955..c4a6a45704 100644\n>>> --- a/environment.h\n>>> +++ b/environment.h\n>>> @@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n>>>\n>>>   void repo_config_values_init(struct repo_config_values *cfg);\n>>>\n>>> +/*\n>>> + * Frees memory allocated for dynamically loaded configuration values\n>>> + * inside `repo_config_values`.\n>>> + *\n>>> + * As dynamically allocated variables are migrated into this struct,\n>>> + * their FREE_AND_NULL() calls should be appended here.\n>>> + */\n>>> +void repo_config_values_clear(struct repository *repo);\n>>\n>> I think that I'm not comfortable having the _init() and the _clear()\n>> functions with different signatures.\n>>\n>> _clear() takes struct repository to dodge a BUG().\n>>\n>> I would like to have both signatures equal, why can't we just do directly:\n>>\n>>    void repo_config_values_clear(struct repo_config_values *cfg)\n>>    {\n>>    \tFREE_AND_NULL(cfg->attributes_file);\n>>    }\n>>\n>> and call from repo_clear():\n>>\n>>    repo_config_values_clear(&repo->config_values_private_)\n>>\n>\n> I particularly agree with your point that the signatures of these two\n> functions should be consistent. I missed it tbh...I will change it in\n> the next reroll.\n>\n> However, I think it makes more sense to refactor to pass in 'struct\n> repository', which is consistent with repo-settings.\n\nWhatever the signature is as long as it's symmetrical, is fine by me. However\nhaving 'struct repository' on _init() just passes the problem that\n_clear() had.\n\nLet's look at initialize_repository():\n\n\tvoid initialize_repository(struct repository *repo)\n\t{\n\t\tif (repo->initialized)\n\t\t\tBUG(\"repository initialized already\");\n\t\trepo->initialized = true;\n\t[snip]\n\t\trepo_config_values_init(&repo->config_values_private_);\n\t[snip]\n\nif we change the _init() signature to receive 'struct repository', how do\nwe access our _private_ field? We cannot call repo_config_values() on\nrepo_config_values_init() because initialize_repository() is also called for\nsubmodules (repo_init() -> initialize_repository()) so we would BUG() out.\nWe would have to access the _private_ field directly at _init().\n\nWith _clear() accessing directly the _private_ field we can forget about\nthe NEEDSWORK, it works for every repository.\n\n>\n>> I get that the workaround might be to not access directly to\n>> &repo->config_values_private_ which repo_config_values() returns but for\n>> example initialize_repository() access this _private_ field directly as\n>> well.\n>\n> Now that we have used the _private_ suffix, if we can just define a\n> _clear() to bypass the assertion of repo_config_values(), wouldn't this\n> be self-deception? I'm not saying that the original lines are\n> necessarily correct... but I do think that semantically speaking, it is\n> inappropriate to pass in config_values_private_ to _clear().\n\nI don't think it would be \"self-deception\": _init() and _clear() are the\nstart and end of this _private_ field's lifecycle so having them receive\n'struct repo_config_values' directly sounds reasonable to me.\n\n>\n>> Also at repo_init(), initialize_repository() sets repo->initialized\n>> before anything can fail and call repo_clear() but\n>> repo_config_values_clear() should be able to free attributes_file even\n>> just after a memset() (which happens before initialize_repository()).\n>> But calling repo_config_values has a BUG() in case of\n>> !repo->initialized are we comfortable with this assert?\n>\n> This goes back to the previous topic: Who is responsible for the call to\n> _clear()? Who is responsible for filtering all those invalid usage of\n> repo instances? Faced with a repo instance that was not initialized but\n> was handed over to _clear() in some way, we have two concepts:\n>\n> - It doesn't matter. Since we always handle config_values_private_, it's\n> NULL at this point, so we don't BUG() and continue.\n>\n> - The very existence of such a repo is a mistake. It shouldn't have\n> appeared and shouldn't have been passed on to me. However, since this\n> situation is relatively common at this point, we choose to return\n> instead of BUG()ing it directly to temporarily avoid it. We will\n> gradually tighten the conditions. When the invalid calls are eliminated\n> in the end, such checks will no longer exist.\n>\n> Our consensus should at least be that this 'use of uninitialized repos'\n> is bad, so they are just two different ways to solve unexpected\n> situations. However, in my opinion, the difference between these two\n> concepts lies in whether we are consciously moving the assertion\n> downward. I think the latter line of thinking does this better.\n\nSounds reasonable, but I think I would go with the first thought of \"It\ndoesn't matter...\".\n\nHaving _clear() access directly removes the need for tightening the\nconditions, because there would be none. The _clear() works for any repo\nas long as it has gone through a memset().\n\nI think that the ones that should ensure that we don't have a \"bad\nrepository\" should be the rest of the code and leave _clear() to just\nclear.\n\n>\n>\n>  >\n>  > Even with the NEEDSWORK it is a silent return, what will happen when\n>  > submodules are supported? If no one remembers to change it we will leak\n>  > the submodules silently.\n>  >\n>\n> I will remember to change it ;)\n>\n> Regards, yuchen\n>\n>>> +\n>>>   /*\n>>>    * TODO: All the below state either explicitly or implicitly relies on\n>>>    * `the_repository`. We should eventually get rid of these and make the\n>>> diff --git a/repository.c b/repository.c\n>>> index 187dd471c4..b31f1b7852 100644\n>>> --- a/repository.c\n>>> +++ b/repository.c\n>>> @@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n>>>   \tFREE_AND_NULL(repo->parsed_objects);\n>>>\n>>>   \trepo_settings_clear(repo);\n>>> +\trepo_config_values_clear(repo);\n>>>\n>>>   \tif (repo->config) {\n>>>   \t\tgit_configset_clear(repo->config);\n>>\n>> Regards,\n>> Pablo\n\nRegards,\nPablo\n"},{"id":"547882","messageId":"DJWHNO2F6ZCW.2V35W5FFY0K3T@gmail.com","threadId":"65873","inReplyTo":"xmqqo6gd9qyr.fsf@gitster.g","subject":"Re: [PATCH v9 0/9] migrate more variables into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-12T09:34:52Z","receivedAt":"2026-07-12T09:34:55Z","isPatch":true,"body":"On Sat Jul 11, 2026 at 11:06 PM CEST, Junio C Hamano wrote:\n> \"Pablo Sabater\" <pabloosabaterr@gmail.com> writes:\n>\n>> On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n>>> ...\n>> Hi!\n>>\n>> I missed a base-commit to easily apply this locally, could we\n>> add one?\n>>\n>> Thanks!\n>> Pablo\n>\n> FYI, a topic that is in 'seen' can be extracted from my tree by\n> inspecting \"git log --oneline origin/master..origin/seen\" and\n> finding the commit that merges the series.\n\nI hadn't thought of that.\n\nThanks, -Pablo\n\n"},{"id":"547883","messageId":"8fe4f7ec-6eaf-4e11-8e6d-cdb6a9a3c347@malon.dev","threadId":"65873","inReplyTo":"DJVY828NHN8O.22CRAQOF73S6D@gmail.com","subject":"Re: [PATCH v9 2/9] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T10:08:31Z","receivedAt":"2026-07-12T10:08:46Z","isPatch":true,"body":"On 7/12/26 02:21, Pablo Sabater wrote:\n> On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n>> The global variable 'excludes_file' is used to track the path to the\n>> global ignore file. If this variable is NULL,\n>> 'setup_standard_excludes()'\n> \n> Nit: Strange line break here.\n> \n>> in 'dir.c' forcefully evaluates and assigns the XDG default path to it.\n>>\n>> Continue the libification effort by encapsulating this lazy-loading\n>> fallback logic into a proper getter and moving the variable into\n>> 'struct repo_config_values'.\n>>\n>> Since 'excludes_file' is a dynamically allocated string, it requires\n>> proper heap memory management. It is safely freed using the newly\n>> introduced `repo_config_values_clear()` function when the repository\n>> is torn down.\n>>\n>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n>> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n>> Signed-off-by: Tian Yuchen <cat@malon.dev>\n>> ---\n>>   dir.c         |  4 ++--\n>>   environment.c | 15 ++++++++++++---\n>>   environment.h |  4 +++-\n>>   3 files changed, 17 insertions(+), 6 deletions(-)\n>>\n>> diff --git a/dir.c b/dir.c\n>> index 7a73690fbc..4f87a52b3c 100644\n>> --- a/dir.c\n>> +++ b/dir.c\n>> @@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n>>\n>>   void setup_standard_excludes(struct dir_struct *dir)\n>>   {\n>> +\tconst char *excludes_file = repo_excludes_file(the_repository);\n>> +\n>>   \tdir->exclude_per_dir = \".gitignore\";\n>>\n>>   \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n>> -\tif (!excludes_file)\n>> -\t\texcludes_file = xdg_config_home(\"ignore\");\n>>   \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n>>   \t\tadd_patterns_from_file_1(dir, excludes_file,\n>>   \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\n>> diff --git a/environment.c b/environment.c\n>> index 13677484de..5950592d63 100644\n>> --- a/environment.c\n>> +++ b/environment.c\n>> @@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n>>   enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n>>   char *editor_program;\n>>   char *askpass_program;\n>> -char *excludes_file;\n>>   enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n>>   enum eol core_eol = EOL_UNSET;\n>>   int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n>> @@ -134,6 +133,14 @@ int is_bare_repository(void)\n>>   \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>>   }\n>>\n>> +const char *repo_excludes_file(struct repository *repo)\n>> +{\n>> +\tif (!repo_config_values(repo)->excludes_file)\n>> +\t\trepo_config_values(repo)->excludes_file = xdg_config_home(\"ignore\");\n>> +\n>> +\treturn repo_config_values(repo)->excludes_file;\n>> +}\n> \n> repo_config_values() returns a pointer so there should be no need to\n> call the function 3 times.\n> \n> We could have the function be called once and use it then:\n> \n>    const char *repo_excludes_file(struct repository *repo)\n>    {\n> \t  struct repo_config_values *cfg = repo_config_values(repo);\n> \n> \t  if (!cfg->excludes_file)\n> \t\t  cfg->excludes_file = xdg_config_home(\"ignore\");\n> \n> \t  return cfg->excludes_file;\n>    }\n> \n\nMakes sense, thanks.\n\n>> +\n>>   int have_git_dir(void)\n>>   {\n>>   \treturn startup_info->have_repository\n>> @@ -461,8 +468,8 @@ int git_default_core_config(const char *var, const char *value,\n>>   \t}\n>>\n>>   \tif (!strcmp(var, \"core.excludesfile\")) {\n>> -\t\tFREE_AND_NULL(excludes_file);\n>> -\t\treturn git_config_pathname(&excludes_file, var, value);\n>> +\t\tFREE_AND_NULL(cfg->excludes_file);\n>> +\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n>>   \t}\n>>\n>>   \tif (!strcmp(var, \"core.whitespace\")) {\n>> @@ -715,6 +722,7 @@ int git_default_config(const char *var, const char *value,\n>>   void repo_config_values_init(struct repo_config_values *cfg)\n>>   {\n>>   \tcfg->attributes_file = NULL;\n>> +\tcfg->excludes_file = NULL;\n>>   \tcfg->apply_sparse_checkout = 0;\n>>   \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>>   \tcfg->trust_ctime = 1;\n>> @@ -744,4 +752,5 @@ void repo_config_values_clear(struct repository *repo)\n>>   \tcfg = repo_config_values(repo);\n>>\n>>   \tFREE_AND_NULL(cfg->attributes_file);\n>> +\tFREE_AND_NULL(cfg->excludes_file);\n>>   }\n>> diff --git a/environment.h b/environment.h\n>> index c4a6a45704..2e8352de7f 100644\n>> --- a/environment.h\n>> +++ b/environment.h\n>> @@ -90,6 +90,7 @@ struct repository;\n>>   struct repo_config_values {\n>>   \t/* section \"core\" config values */\n>>   \tchar *attributes_file;\n>> +\tchar *excludes_file;\n>>   \tint apply_sparse_checkout;\n>>   \tint trust_ctime;\n>>   \tint check_stat;\n>> @@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n>>   int git_default_core_config(const char *var, const char *value,\n>>   \t\t\t    const struct config_context *ctx, void *cb);\n>>\n>> +const char *repo_excludes_file(struct repository *repo);\n>> +\n>>   void repo_config_values_init(struct repo_config_values *cfg);\n>>\n>>   /*\n>> @@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n>>\n>>   extern char *editor_program;\n>>   extern char *askpass_program;\n>> -extern char *excludes_file;\n>>\n>>   /*\n>>    * The character that begins a commented line in user-editable file\n> \n> The rest looks fine.\n> \n> Regards,\n> Pablo\n\nRegards, yuchen\n\n"},{"id":"547885","messageId":"1c8ca470-1607-499b-9d78-e2c0a7a7f4b0@malon.dev","threadId":"65873","inReplyTo":"DJWHIWVJ52UW.24D8DAKBNDMLB@gmail.com","subject":"Re: [PATCH v9 1/9] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T10:44:23Z","receivedAt":"2026-07-12T10:44:30Z","isPatch":true,"body":"Hey Pablo,\n\nOn 7/12/26 17:28, Pablo Sabater wrote:\n> On Sat Jul 11, 2026 at 8:35 PM CEST, Tian Yuchen wrote:\n>> Hii Pablo,\n>>\n>> On 7/12/26 01:21, Pablo Sabater wrote:\n>>> On Thu Jul 9, 2026 at 6:11 PM CEST, Tian Yuchen wrote:\n>>>> As part of the ongoing libification effort, dynamically allocated\n>>>> global configuration variables are being moved into\n>>>> 'struct repo_config_values'. To prevent memory leaks, we need a\n>>>> destructor to free these heap-allocated variables when a repository\n>>>> instance is torn down.\n>>>>\n>>>> Introduce 'repo_config_values_clear()' in environment.c and invoke it\n>>>> from 'repo_clear()' in repository.c. As a starting point, update this\n>>>> new function to handle the cleanup of 'attributes_file'.\n>>>\n>>> Makes sense.\n>>>\n>>>>\n>>>> Note:\n>>>>\n>>>> Submodules are currently not supported by repo_config_values(), which\n>>>> explicitly BUG()s out if 'repo != the_repository'. Since repo_clear()\n>>>> cleans up all repository instances, we must bypass them to prevent\n>>>> crashing.\n>>>>\n>>>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>>>> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n>>>> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n>>>> Signed-off-by: Tian Yuchen <cat@malon.dev>\n>>>> ---\n>>>>    environment.c | 19 +++++++++++++++++++\n>>>>    environment.h |  9 +++++++++\n>>>>    repository.c  |  1 +\n>>>>    3 files changed, 29 insertions(+)\n>>>>\n>>>> diff --git a/environment.c b/environment.c\n>>>> index ba2c60103f..13677484de 100644\n>>>> --- a/environment.c\n>>>> +++ b/environment.c\n>>>> @@ -726,3 +726,22 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>>>>    \tcfg->sparse_expect_files_outside_of_patterns = 0;\n>>>>    \tcfg->warn_on_object_refname_ambiguity = 1;\n>>>>    }\n>>>> +\n>>>> +void repo_config_values_clear(struct repository *repo)\n>>>> +{\n>>>> +\tstruct repo_config_values *cfg;\n>>>> +\n>>>> +\t/*\n>>>> +\t * NEEDSWORK: Submodules are currently not supported by\n>>>> +\t * repo_config_values(), which explicitly BUG()s out if\n>>>> +\t * repo != the_repository. Since repo_clear() cleans up all\n>>>> +\t * repository instances, we must bypass them here to prevent\n>>>> +\t * crashing.\n>>>> +\t */\n>>>> +\tif (repo != the_repository)\n>>>> +\t\treturn;\n>>>> +\n>>>> +\tcfg = repo_config_values(repo);\n>>>> +\n>>>> +\tFREE_AND_NULL(cfg->attributes_file);\n>>>> +}\n>>>> diff --git a/environment.h b/environment.h\n>>>> index 6f18286955..c4a6a45704 100644\n>>>> --- a/environment.h\n>>>> +++ b/environment.h\n>>>> @@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n>>>>\n>>>>    void repo_config_values_init(struct repo_config_values *cfg);\n>>>>\n>>>> +/*\n>>>> + * Frees memory allocated for dynamically loaded configuration values\n>>>> + * inside `repo_config_values`.\n>>>> + *\n>>>> + * As dynamically allocated variables are migrated into this struct,\n>>>> + * their FREE_AND_NULL() calls should be appended here.\n>>>> + */\n>>>> +void repo_config_values_clear(struct repository *repo);\n>>>\n>>> I think that I'm not comfortable having the _init() and the _clear()\n>>> functions with different signatures.\n>>>\n>>> _clear() takes struct repository to dodge a BUG().\n>>>\n>>> I would like to have both signatures equal, why can't we just do directly:\n>>>\n>>>     void repo_config_values_clear(struct repo_config_values *cfg)\n>>>     {\n>>>     \tFREE_AND_NULL(cfg->attributes_file);\n>>>     }\n>>>\n>>> and call from repo_clear():\n>>>\n>>>     repo_config_values_clear(&repo->config_values_private_)\n>>>\n>>\n>> I particularly agree with your point that the signatures of these two\n>> functions should be consistent. I missed it tbh...I will change it in\n>> the next reroll.\n>>\n>> However, I think it makes more sense to refactor to pass in 'struct\n>> repository', which is consistent with repo-settings.\n> \n> Whatever the signature is as long as it's symmetrical, is fine by me. However\n> having 'struct repository' on _init() just passes the problem that\n> _clear() had.\n> \n> Let's look at initialize_repository():\n> \n> \tvoid initialize_repository(struct repository *repo)\n> \t{\n> \t\tif (repo->initialized)\n> \t\t\tBUG(\"repository initialized already\");\n> \t\trepo->initialized = true;\n> \t[snip]\n> \t\trepo_config_values_init(&repo->config_values_private_);\n> \t[snip]\n> \n> if we change the _init() signature to receive 'struct repository', how do\n> we access our _private_ field? We cannot call repo_config_values() on\n> repo_config_values_init() because initialize_repository() is also called for\n> submodules (repo_init() -> initialize_repository()) so we would BUG() out.\n> We would have to access the _private_ field directly at _init().\n> \n> With _clear() accessing directly the _private_ field we can forget about\n> the NEEDSWORK, it works for every repository.\n> \n>>\n>>> I get that the workaround might be to not access directly to\n>>> &repo->config_values_private_ which repo_config_values() returns but for\n>>> example initialize_repository() access this _private_ field directly as\n>>> well.\n>>\n>> Now that we have used the _private_ suffix, if we can just define a\n>> _clear() to bypass the assertion of repo_config_values(), wouldn't this\n>> be self-deception? I'm not saying that the original lines are\n>> necessarily correct... but I do think that semantically speaking, it is\n>> inappropriate to pass in config_values_private_ to _clear().\n> \n> I don't think it would be \"self-deception\": _init() and _clear() are the\n> start and end of this _private_ field's lifecycle so having them receive\n> 'struct repo_config_values' directly sounds reasonable to me.\n> \n>>\n>>> Also at repo_init(), initialize_repository() sets repo->initialized\n>>> before anything can fail and call repo_clear() but\n>>> repo_config_values_clear() should be able to free attributes_file even\n>>> just after a memset() (which happens before initialize_repository()).\n>>> But calling repo_config_values has a BUG() in case of\n>>> !repo->initialized are we comfortable with this assert?\n>>\n>> This goes back to the previous topic: Who is responsible for the call to\n>> _clear()? Who is responsible for filtering all those invalid usage of\n>> repo instances? Faced with a repo instance that was not initialized but\n>> was handed over to _clear() in some way, we have two concepts:\n>>\n>> - It doesn't matter. Since we always handle config_values_private_, it's\n>> NULL at this point, so we don't BUG() and continue.\n>>\n>> - The very existence of such a repo is a mistake. It shouldn't have\n>> appeared and shouldn't have been passed on to me. However, since this\n>> situation is relatively common at this point, we choose to return\n>> instead of BUG()ing it directly to temporarily avoid it. We will\n>> gradually tighten the conditions. When the invalid calls are eliminated\n>> in the end, such checks will no longer exist.\n>>\n>> Our consensus should at least be that this 'use of uninitialized repos'\n>> is bad, so they are just two different ways to solve unexpected\n>> situations. However, in my opinion, the difference between these two\n>> concepts lies in whether we are consciously moving the assertion\n>> downward. I think the latter line of thinking does this better.\n> \n> Sounds reasonable, but I think I would go with the first thought of \"It\n> doesn't matter...\".\n> \n> Having _clear() access directly removes the need for tightening the\n> conditions, because there would be none. The _clear() works for any repo\n> as long as it has gone through a memset().\n> \n> I think that the ones that should ensure that we don't have a \"bad\n> repository\" should be the rest of the code and leave _clear() to just\n> clear.\n> \n>>\n>>\n>>   >\n>>   > Even with the NEEDSWORK it is a silent return, what will happen when\n>>   > submodules are supported? If no one remembers to change it we will leak\n>>   > the submodules silently.\n>>   >\n>>\n>> I will remember to change it ;)\n>>\n>> Regards, yuchen\n>>\n>>>> +\n>>>>    /*\n>>>>     * TODO: All the below state either explicitly or implicitly relies on\n>>>>     * `the_repository`. We should eventually get rid of these and make the\n>>>> diff --git a/repository.c b/repository.c\n>>>> index 187dd471c4..b31f1b7852 100644\n>>>> --- a/repository.c\n>>>> +++ b/repository.c\n>>>> @@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n>>>>    \tFREE_AND_NULL(repo->parsed_objects);\n>>>>\n>>>>    \trepo_settings_clear(repo);\n>>>> +\trepo_config_values_clear(repo);\n>>>>\n>>>>    \tif (repo->config) {\n>>>>    \t\tgit_configset_clear(repo->config);\n>>>\n>>> Regards,\n>>> Pablo\n> \n> Regards,\n> Pablo\n\nI thought about it again, and okay, I have to admit your approach is \nindeed more reasonable. Maybe it was just my stubbornness stemming from \nstaying up late thinking last night. :D\n\nThanks! yuchen\n"},{"id":"547886","messageId":"20260712111734.1073514-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260709161145.13349-1-cat@malon.dev","subject":"[PATCH v10 0/9] migrate more variables into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:24Z","receivedAt":"2026-07-12T11:17:41Z","isPatch":true,"body":"Hi everyone,\n\nThis patch series continues the ongoing libification effort by migrating\na batch of global configuration variables into struct repo_config_values.\n\nWhat does this series do:\n\ninfrastructure & strings (commits 1-6):\nIntroduce 'repo_config_values_clear()' to manage the lifecycle\nof heap-allocated configuration strings. This infrastructure is utilized\nto migrate string variables, including 'excludes_file', 'apply' whitespace\nconfigs, and external programs including 'editor', 'pager', 'askpass'.\n\nenums (commits 7-9):\nMigrate enumerations 'push_default', 'autorebase', and\n'object_creation_mode'. Care was taken to make these types available\nto the configuration structure without triggering circular header\ndependencies.\n\nRFC:\n\nCommit 3~5. Is it really necessary to migrate _program variables?\nhttps://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n\nCommit 6~9. Previous related discussions on 'git_branch_track'.\nhttps://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n\nNote:\n\nSince a new getter 'repo_excludes_file()' is introduced, as previously\npromised, once it is finally merged into 'master', there will be a patch to\nupdate and squash the comments.\n\nSimilarly, I've noticed that the classification and sorting of variables in\n'repo_config_values' don't seem to be correct. There will also be a patch\nto fix this, and I think it will form a commit series along with the comment\npatch?\n\nChanges since v9:\n\n - Fixed a few typos.\n\n - Drop an unnecessary branching in editor.c\n\n - Use repo_config_values *cfg to avoid multiple calls to\n repo_config_values() in repo_excludes_file().\n\n - Let repo_config_values_clear() receive 'struct repo_config_values' and\n do not check (repo != the_repository) anymore. In repo_clear(), pass in\n config_values_private_ instead so that we don't need to filter calls with\n uninitialized repos. Therefore, drop the NEEDSWORK comment. \n\nSpecial thanks to Pablo!\n\nTian Yuchen (9):\n  repository: introduce repo_config_values_clear()\n  environment: move excludes_file into repo_config_values\n  environment: move editor_program into repo_config_values\n  environment: move pager_program into repo_config_values\n  environment: move askpass_program into repo_config_values\n  environment: migrate apply_default_whitespace and\n    apply_default_ignorewhitespace\n  environment: move push_default into repo_config_values\n  environment: move autorebase into repo_config_values\n  environment: move object_creation_mode into repo_config_values\n\n apply.c        | 20 ++++++++------\n branch.c       |  2 +-\n builtin/push.c |  8 +++---\n dir.c          |  4 +--\n editor.c       |  4 +--\n environment.c  | 75 +++++++++++++++++++++++++++++++++-----------------\n environment.h  | 75 +++++++++++++++++++++++++++++++-------------------\n object-file.c  |  2 +-\n pager.c        | 26 +++++++++++------\n prompt.c       |  3 +-\n remote.c       |  2 +-\n repository.c   |  1 +\n 12 files changed, 140 insertions(+), 82 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"547887","messageId":"20260712111734.1073514-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 1/9] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:25Z","receivedAt":"2026-07-12T11:17:46Z","isPatch":true,"body":"As part of the ongoing libification effort, dynamically allocated\nglobal configuration variables are being moved into\n'struct repo_config_values'. To prevent memory leaks, we need a\ndestructor to free these heap-allocated variables when a repository\ninstance is torn down.\n\nIntroduce 'repo_config_values_clear()' in environment.c and invoke it\nfrom 'repo_clear()' in repository.c. As a starting point, update this\nnew function to handle the cleanup of 'attributes_file'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 5 +++++\n environment.h | 9 +++++++++\n repository.c  | 1 +\n 3 files changed, 15 insertions(+)\n\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..ae05f16d04 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -726,3 +726,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repo_config_values *cfg)\n+{\n+\tFREE_AND_NULL(cfg->attributes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..9169d7f62d 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n \n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repo_config_values *cfg);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..669e2d1200 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(&repo->config_values_private_);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"547889","messageId":"20260712111734.1073514-4-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 3/9] environment: move editor_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:27Z","receivedAt":"2026-07-12T11:17:53Z","isPatch":true,"body":"The global variable 'editor_program' holds the path to the user's\npreferred editor. Move 'editor_program' into\n'struct repo_config_values' to continue the libification effort.\n\nThere have been discussions on whether external programs like\neditors truly need to be configured on a per-repository basis within\nthe same process. While a single process might rarely invoke\ndifferent editors, this migration is necessary for two reasons:\n\n1. Developers frequently use different toolchains for different\n   projects. Per-repo configuration respects this.\n\n2. Moving this string into 'repo_config_values' eliminates mutable\n   global state. As the codebase moves toward becoming a long-running\n   processes, managing multiple repositories concurrently must\n   not overwrite each other's program configurations.\n\nNo standalone getter function is introduced. Callers directly access\nthe field via 'repo_config_values()'. Heap memory is safely reclaimed\nin 'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n editor.c      | 4 ++--\n environment.c | 7 ++++---\n environment.h | 2 +-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/editor.c b/editor.c\nindex fd174e6a03..0d1cb8768d 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -29,8 +29,8 @@ const char *git_editor(void)\n \tconst char *editor = getenv(\"GIT_EDITOR\");\n \tint terminal_is_dumb = is_terminal_dumb();\n \n-\tif (!editor && editor_program)\n-\t\teditor = editor_program;\n+\tif (!editor)\n+\t\teditor = repo_config_values(the_repository)->editor_program;\n \tif (!editor && !terminal_is_dumb)\n \t\teditor = getenv(\"VISUAL\");\n \tif (!editor)\ndiff --git a/environment.c b/environment.c\nindex 275931c213..a65d575af4 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *editor_program;\n char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n@@ -437,8 +436,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.editor\")) {\n-\t\tFREE_AND_NULL(editor_program);\n-\t\treturn git_config_string(&editor_program, var, value);\n+\t\tFREE_AND_NULL(cfg->editor_program);\n+\t\treturn git_config_string(&cfg->editor_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n@@ -725,6 +724,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n+\tcfg->editor_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -741,4 +741,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n {\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n+\tFREE_AND_NULL(cfg->editor_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 4776ccc657..8178ebab76 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -91,6 +91,7 @@ struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n \tchar *excludes_file;\n+\tchar *editor_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *editor_program;\n extern char *askpass_program;\n \n /*\n-- \n2.43.0\n\n"},{"id":"547888","messageId":"20260712111734.1073514-3-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 2/9] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:26Z","receivedAt":"2026-07-12T11:17:54Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL,\n'setup_standard_excludes()' in 'dir.c' forcefully evaluates and assigns\nthe XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. It is safely freed using the newly\nintroduced 'repo_config_values_clear()' function when the repository\nis torn down.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 17 ++++++++++++++---\n environment.h |  4 +++-\n 3 files changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex ae05f16d04..275931c213 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,16 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n+\n+\tif (!cfg->excludes_file)\n+\t\tcfg->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn cfg->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +470,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +724,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -730,4 +740,5 @@ void repo_config_values_init(struct repo_config_values *cfg)\n void repo_config_values_clear(struct repo_config_values *cfg)\n {\n \tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n }\ndiff --git a/environment.h b/environment.h\nindex 9169d7f62d..4776ccc657 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\n-- \n2.43.0\n\n"},{"id":"547890","messageId":"20260712111734.1073514-5-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 4/9] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:28Z","receivedAt":"2026-07-12T11:17:57Z","isPatch":true,"body":"The 'pager_program' variable is currently defined as a file-scoped\nstatic string in pager.c. Move it into 'struct repo_config_values'.\n\nThe configuration parsing logic remains strictly within pager.c to\nrespect subsystem boundaries. The read/write operations are simply\nredirected to the repository-specific structure using\n'repo_config_values()'.\n\nSimilar to the recent editor_program migration, no standalone getter\nis introduced to keep the code minimal. The dynamically allocated\nmemory is now managed by 'repo_config_values_clear()'.\n\nOn top of that, fix a memory leak in pager.c while we are at it.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  2 ++\n environment.h |  1 +\n pager.c       | 26 +++++++++++++++++---------\n 3 files changed, 20 insertions(+), 9 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex a65d575af4..975c9cb9eb 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -725,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n+\tcfg->pager_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -742,4 +743,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n+\tFREE_AND_NULL(cfg->pager_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 8178ebab76..39b6691b47 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tchar *excludes_file;\n \tchar *editor_program;\n+\tchar *pager_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/pager.c b/pager.c\nindex 35b210e048..bc55546670 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -5,6 +5,8 @@\n #include \"run-command.h\"\n #include \"sigchain.h\"\n #include \"alias.h\"\n+#include \"repository.h\"\n+#include \"environment.h\"\n \n int pager_use_color = 1;\n \n@@ -13,7 +15,6 @@ int pager_use_color = 1;\n #endif\n \n static struct child_process pager_process;\n-static char *pager_program;\n static int old_fd1 = -1, old_fd2 = -1;\n \n /* Is the value coming back from term_columns() just a guess? */\n@@ -75,10 +76,15 @@ static void wait_for_pager_signal(int signo)\n \n static int core_pager_config(const char *var, const char *value,\n \t\t\t     const struct config_context *ctx UNUSED,\n-\t\t\t     void *data UNUSED)\n+\t\t\t     void *data)\n {\n-\tif (!strcmp(var, \"core.pager\"))\n-\t\treturn git_config_string(&pager_program, var, value);\n+\tstruct repository *r = data;\n+\n+\tif (!strcmp(var, \"core.pager\")) {\n+\t\tFREE_AND_NULL(repo_config_values(r)->pager_program);\n+\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n+\t}\n+\n \treturn 0;\n }\n \n@@ -91,10 +97,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n \n \tpager = getenv(\"GIT_PAGER\");\n \tif (!pager) {\n-\t\tif (!pager_program)\n+\t\tif (!repo_config_values(r)->pager_program)\n \t\t\tread_early_config(r,\n-\t\t\t\t\t  core_pager_config, NULL);\n-\t\tpager = pager_program;\n+\t\t\t\t\t  core_pager_config, r);\n+\t\tpager = repo_config_values(r)->pager_program;\n \t}\n \tif (!pager)\n \t\tpager = getenv(\"PAGER\");\n@@ -302,7 +308,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n \n \tread_early_config(r, pager_command_config, &data);\n \n-\tif (data.value)\n-\t\tpager_program = data.value;\n+\tif (data.value) {\n+\t\tfree(repo_config_values(r)->pager_program);\n+\t\trepo_config_values(r)->pager_program = data.value;\n+\t}\n \treturn data.want;\n }\n-- \n2.43.0\n\n"},{"id":"547891","messageId":"20260712111734.1073514-6-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 5/9] environment: move askpass_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:29Z","receivedAt":"2026-07-12T11:17:59Z","isPatch":true,"body":"The global variable 'askpass_program' stores the path to the program\nused to prompt the user for credentials. Move it into repo_config_values\nto continue the libification effort.\n\nWhile it is uncommon for a single process to require different askpass\nprograms for different repositories, maintaining this value as a mutable\nglobal string is a blocker for libification. Global heap-allocated\nstrings introduce thread-safety issues in a multi-repo environment.\n\nMove 'askpass_program' into 'struct repo_config_values' to eliminate\nthis global state. The memory is now safely managed and freed via\n'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 6 ++++--\n environment.h | 1 +\n prompt.c      | 3 ++-\n 3 files changed, 7 insertions(+), 3 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 975c9cb9eb..1a26c9c6d6 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -464,8 +464,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.askpass\")) {\n-\t\tFREE_AND_NULL(askpass_program);\n-\t\treturn git_config_string(&askpass_program, var, value);\n+\t\tFREE_AND_NULL(cfg->askpass_program);\n+\t\treturn git_config_string(&cfg->askpass_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n@@ -726,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n+\tcfg->askpass_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -744,4 +745,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n+\tFREE_AND_NULL(cfg->askpass_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 39b6691b47..a2e9def89d 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -93,6 +93,7 @@ struct repo_config_values {\n \tchar *excludes_file;\n \tchar *editor_program;\n \tchar *pager_program;\n+\tchar *askpass_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/prompt.c b/prompt.c\nindex 706fba2a50..d8d74c7e37 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -3,6 +3,7 @@\n #include \"git-compat-util.h\"\n #include \"parse.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"prompt.h\"\n@@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)\n \n \t\taskpass = getenv(\"GIT_ASKPASS\");\n \t\tif (!askpass)\n-\t\t\taskpass = askpass_program;\n+\t\t\taskpass = repo_config_values(the_repository)->askpass_program;\n \t\tif (!askpass)\n \t\t\taskpass = getenv(\"SSH_ASKPASS\");\n \t\tif (askpass && *askpass)\n-- \n2.43.0\n\n"},{"id":"547892","messageId":"20260712111734.1073514-7-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 6/9] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:30Z","receivedAt":"2026-07-12T11:18:02Z","isPatch":true,"body":"The global variables 'apply_default_whitespace' and\n'apply_default_ignorewhitespace' are used to store the default\nwhitespace configuration for 'git apply'. Move these variables\ninto 'struct repo_config_values' to continue the libification\neffort.\n\nDynamically allocated strings fetched via 'repo_config_get_string()'\nare now tracked per-repository and safely freed in\n'repo_config_values_clear()'.\n\nAs part of this transition, update 'git_apply_config()' to accept a\n'struct repository *' argument rather than relying on the\n'the_repository' global.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n apply.c       | 20 ++++++++++++--------\n environment.c |  6 ++++--\n environment.h |  4 ++--\n 3 files changed, 18 insertions(+), 12 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..66db9b7678 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -47,11 +47,13 @@ struct gitdiff_data {\n \tint p_value;\n };\n \n-static void git_apply_config(void)\n+static void git_apply_config(struct repository *repo)\n {\n-\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n-\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n-\trepo_config(the_repository, git_xmerge_config, NULL);\n+\trepo_config_get_string(repo, \"apply.whitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_whitespace);\n+\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n+\t\t\t       &repo_config_values(repo)->apply_default_ignorewhitespace);\n+\trepo_config(repo, git_xmerge_config, NULL);\n }\n \n static int parse_whitespace_option(struct apply_state *state, const char *option)\n@@ -126,10 +128,12 @@ int init_apply_state(struct apply_state *state,\n \tstrset_init(&state->kept_symlinks);\n \tstrbuf_init(&state->root, 0);\n \n-\tgit_apply_config();\n-\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n+\tgit_apply_config(repo);\n+\tif (repo_config_values(repo)->apply_default_whitespace &&\n+\t    parse_whitespace_option(state, repo_config_values(repo)->apply_default_whitespace))\n \t\treturn -1;\n-\tif (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))\n+\tif (repo_config_values(repo)->apply_default_ignorewhitespace &&\n+\t    parse_ignorewhitespace_option(state, repo_config_values(repo)->apply_default_ignorewhitespace))\n \t\treturn -1;\n \treturn 0;\n }\n@@ -192,7 +196,7 @@ int check_apply_state(struct apply_state *state, int force_apply)\n \n static void set_default_whitespace_mode(struct apply_state *state)\n {\n-\tif (!state->whitespace_option && !apply_default_whitespace)\n+\tif (!state->whitespace_option && !repo_config_values(state->repo)->apply_default_whitespace)\n \t\tstate->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);\n }\n \ndiff --git a/environment.c b/environment.c\nindex 1a26c9c6d6..41ba013c86 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,8 +49,6 @@ int assume_unchanged;\n int is_bare_repository_cfg = -1; /* unspecified */\n char *git_commit_encoding;\n char *git_log_output_encoding;\n-char *apply_default_whitespace;\n-char *apply_default_ignorewhitespace;\n int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n@@ -727,6 +725,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n \tcfg->askpass_program = NULL;\n+\tcfg->apply_default_whitespace = NULL;\n+\tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -746,4 +746,6 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n \tFREE_AND_NULL(cfg->askpass_program);\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n }\ndiff --git a/environment.h b/environment.h\nindex a2e9def89d..553f87adee 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -94,6 +94,8 @@ struct repo_config_values {\n \tchar *editor_program;\n \tchar *pager_program;\n \tchar *askpass_program;\n+\tchar *apply_default_whitespace;\n+\tchar *apply_default_ignorewhitespace;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -182,8 +184,6 @@ extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\n-extern char *apply_default_whitespace;\n-extern char *apply_default_ignorewhitespace;\n extern unsigned long pack_size_limit_cfg;\n \n extern int protect_hfs;\n-- \n2.43.0\n\n"},{"id":"547893","messageId":"20260712111734.1073514-8-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 7/9] environment: move push_default into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:31Z","receivedAt":"2026-07-12T11:18:10Z","isPatch":true,"body":"The global variable 'push_default' specifies the default behavior of\n'git push' when no explicit refspec is provided. Move 'push_default'\ninto 'struct repo_config_values' to continue the libification effort.\n\nWhile 'enum push_default_type' ideally belongs in 'remote.h', moving it\nthere introduces a circular dependency chain:\n\n  remote.h -> hash.h -> repository.h -> environment.h.\n\nTherefore, the enum definition is kept in 'environment.h' just above\n'struct repo_config_values' with a NEEDSWORK comment for future cleanup.\n\nModify the configuration parsing in environment.c to update the\nper-repository structure directly, and update caller across the\ncodebase to access the value via 'repo_config_values()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n builtin/push.c |  8 ++++----\n environment.c  | 16 +++++++++-------\n environment.h  | 26 ++++++++++++++++----------\n remote.c       |  2 +-\n 4 files changed, 30 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..6dc3224b60 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -88,7 +88,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,\n \t\t}\n \t}\n \n-\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n+\tif (repo_config_values(the_repository)->push_default == PUSH_DEFAULT_UPSTREAM &&\n \t    skip_prefix(matched->name, \"refs/heads/\", &branch_name)) {\n \t\tstruct branch *branch = branch_get(branch_name);\n \t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n@@ -160,7 +160,7 @@ static NORETURN void die_push_simple(struct branch *branch,\n \t * Don't show advice for people who explicitly set\n \t * push.default.\n \t */\n-\tif (push_default == PUSH_DEFAULT_UNSPECIFIED)\n+\tif (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)\n \t\tadvice_pushdefault_maybe = _(\"\\n\"\n \t\t\t\t \"To choose either option permanently, \"\n \t\t\t\t \"see push.default in 'git help config'.\\n\");\n@@ -232,7 +232,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tconst char *dst;\n \tint same_remote;\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tcase PUSH_DEFAULT_MATCHING:\n \t\trefspec_append(&rs, \":\");\n \t\treturn;\n@@ -252,7 +252,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tdst = branch->refname;\n \tsame_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(the_repository)->push_default) {\n \tdefault:\n \tcase PUSH_DEFAULT_UNSPECIFIED:\n \tcase PUSH_DEFAULT_SIMPLE:\ndiff --git a/environment.c b/environment.c\nindex 41ba013c86..0080012f31 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -59,7 +59,6 @@ enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n-enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -621,21 +620,23 @@ static int git_default_branch_config(const char *var, const char *value)\n \n static int git_default_push_config(const char *var, const char *value)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tif (!strcmp(var, \"push.default\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"nothing\"))\n-\t\t\tpush_default = PUSH_DEFAULT_NOTHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_NOTHING;\n \t\telse if (!strcmp(value, \"matching\"))\n-\t\t\tpush_default = PUSH_DEFAULT_MATCHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_MATCHING;\n \t\telse if (!strcmp(value, \"simple\"))\n-\t\t\tpush_default = PUSH_DEFAULT_SIMPLE;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_SIMPLE;\n \t\telse if (!strcmp(value, \"upstream\"))\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"tracking\")) /* deprecated */\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"current\"))\n-\t\t\tpush_default = PUSH_DEFAULT_CURRENT;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_CURRENT;\n \t\telse {\n \t\t\terror(_(\"malformed value for %s: %s\"), var, value);\n \t\t\treturn error(_(\"must be one of nothing, matching, simple, \"\n@@ -727,6 +728,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->askpass_program = NULL;\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n+\tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 553f87adee..6a5c8bd06f 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -87,6 +87,21 @@ extern const char * const local_repo_env[];\n struct strvec;\n \n struct repository;\n+\n+/*\n+ * NEEDSWORK: It would be better if these definitions could be moved to\n+ * other more specific files, but care is needed to avoid circular\n+ * inclusion issues.\n+ */\n+enum push_default_type {\n+\tPUSH_DEFAULT_NOTHING = 0,\n+\tPUSH_DEFAULT_MATCHING,\n+\tPUSH_DEFAULT_SIMPLE,\n+\tPUSH_DEFAULT_UPSTREAM,\n+\tPUSH_DEFAULT_CURRENT,\n+\tPUSH_DEFAULT_UNSPECIFIED\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -96,6 +111,7 @@ struct repo_config_values {\n \tchar *askpass_program;\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n+\tenum push_default_type push_default;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -197,16 +213,6 @@ enum rebase_setup_type {\n };\n extern enum rebase_setup_type autorebase;\n \n-enum push_default_type {\n-\tPUSH_DEFAULT_NOTHING = 0,\n-\tPUSH_DEFAULT_MATCHING,\n-\tPUSH_DEFAULT_SIMPLE,\n-\tPUSH_DEFAULT_UPSTREAM,\n-\tPUSH_DEFAULT_CURRENT,\n-\tPUSH_DEFAULT_UNSPECIFIED\n-};\n-extern enum push_default_type push_default;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..d48c01d375 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo,\n \tif (remote->mirror)\n \t\treturn tracking_for_push_dest(remote, branch->refname, err);\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(repo)->push_default) {\n \tcase PUSH_DEFAULT_NOTHING:\n \t\treturn error_buf(err, _(\"push has no destination (push.default is 'nothing')\"));\n \n-- \n2.43.0\n\n"},{"id":"547894","messageId":"20260712111734.1073514-9-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 8/9] environment: move autorebase into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:32Z","receivedAt":"2026-07-12T11:18:11Z","isPatch":true,"body":"The global variable 'autorebase' dictates whether a newly created\nbranch should be configured to automatically rebase by default.\nMove it into 'struct repo_config_values' to continue the\nlibification effort.\n\nThe 'enum rebase_setup_type' definition is moved higher up in\n'environment.h' so that it is visible to the repository-specific\nstructure. The default state AUTOREBASE_NEVER is now correctly\ninitialized in 'repo_config_values_init()'.\n\nConfiguration parsing in 'git_default_branch_config()' is updated to\nwrite directly to the repository's configuration instance.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n branch.c      |  2 +-\n environment.c | 10 +++++-----\n environment.h | 16 ++++++++--------\n 3 files changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 243db7d0fc..e1c1f8c89d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n static int should_setup_rebase(const char *origin)\n {\n-\tswitch (autorebase) {\n+\tswitch (repo_config_values(the_repository)->autorebase) {\n \tcase AUTOREBASE_NEVER:\n \t\treturn 0;\n \tcase AUTOREBASE_LOCAL:\ndiff --git a/environment.c b/environment.c\nindex 0080012f31..42829a9c7a 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -58,7 +58,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n-enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -602,13 +601,13 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"never\"))\n-\t\t\tautorebase = AUTOREBASE_NEVER;\n+\t\t\tcfg->autorebase = AUTOREBASE_NEVER;\n \t\telse if (!strcmp(value, \"local\"))\n-\t\t\tautorebase = AUTOREBASE_LOCAL;\n+\t\t\tcfg->autorebase = AUTOREBASE_LOCAL;\n \t\telse if (!strcmp(value, \"remote\"))\n-\t\t\tautorebase = AUTOREBASE_REMOTE;\n+\t\t\tcfg->autorebase = AUTOREBASE_REMOTE;\n \t\telse if (!strcmp(value, \"always\"))\n-\t\t\tautorebase = AUTOREBASE_ALWAYS;\n+\t\t\tcfg->autorebase = AUTOREBASE_ALWAYS;\n \t\telse\n \t\t\treturn error(_(\"malformed value for %s\"), var);\n \t\treturn 0;\n@@ -729,6 +728,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n+\tcfg->autorebase = AUTOREBASE_NEVER;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 6a5c8bd06f..deabc5ef30 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -102,6 +102,13 @@ enum push_default_type {\n \tPUSH_DEFAULT_UNSPECIFIED\n };\n \n+enum rebase_setup_type {\n+\tAUTOREBASE_NEVER = 0,\n+\tAUTOREBASE_LOCAL,\n+\tAUTOREBASE_REMOTE,\n+\tAUTOREBASE_ALWAYS\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -112,6 +119,7 @@ struct repo_config_values {\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n+\tenum rebase_setup_type autorebase;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -205,14 +213,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum rebase_setup_type {\n-\tAUTOREBASE_NEVER = 0,\n-\tAUTOREBASE_LOCAL,\n-\tAUTOREBASE_REMOTE,\n-\tAUTOREBASE_ALWAYS\n-};\n-extern enum rebase_setup_type autorebase;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\n-- \n2.43.0\n\n"},{"id":"547895","messageId":"20260712111734.1073514-10-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v10 9/9] environment: move object_creation_mode into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T11:17:33Z","receivedAt":"2026-07-12T11:18:12Z","isPatch":true,"body":"The global variable 'object_creation_mode' controls how Git creates\nobject files, specifically determining whether to use hardlinks or\nrenames when moving temporary files into the object database. Move\nit into 'struct repo_config_values' to continue the libification\neffort.\n\nMove the 'enum object_creation_mode' definition higher up in\n'environment.h' to ensure it is visible to the structure. Initialize\nthe per-repository value to its default macro value\nOBJECT_CREATION_MODE inside 'repo_config_values_init()'.\n\nUpdate configuration parsing in 'git_default_core_config()' to write\ndirectly to the repository-specific configuration structure.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  6 +++---\n environment.h | 12 ++++++------\n object-file.c |  2 +-\n 3 files changed, 10 insertions(+), 10 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 42829a9c7a..e882e4ada2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -61,7 +61,6 @@ char *check_roundtrip_encoding;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n-enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;\n int grafts_keep_true_parents;\n unsigned long pack_size_limit_cfg;\n \n@@ -513,9 +512,9 @@ int git_default_core_config(const char *var, const char *value,\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\tif (!strcmp(value, \"rename\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_RENAMES;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES;\n \t\telse if (!strcmp(value, \"link\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n \t\telse\n \t\t\tdie(_(\"invalid mode for object creation: %s\"), value);\n \t\treturn 0;\n@@ -729,6 +728,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->autorebase = AUTOREBASE_NEVER;\n+\tcfg->object_creation_mode = OBJECT_CREATION_MODE;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex deabc5ef30..de6e80cce2 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -109,6 +109,11 @@ enum rebase_setup_type {\n \tAUTOREBASE_ALWAYS\n };\n \n+enum object_creation_mode {\n+\tOBJECT_CREATION_USES_HARDLINKS = 0,\n+\tOBJECT_CREATION_USES_RENAMES = 1\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -120,6 +125,7 @@ struct repo_config_values {\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n \tenum rebase_setup_type autorebase;\n+\tenum object_creation_mode object_creation_mode;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -213,12 +219,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum object_creation_mode {\n-\tOBJECT_CREATION_USES_HARDLINKS = 0,\n-\tOBJECT_CREATION_USES_RENAMES = 1\n-};\n-extern enum object_creation_mode object_creation_mode;\n-\n extern int grafts_keep_true_parents;\n \n const char *get_log_output_encoding(void);\ndiff --git a/object-file.c b/object-file.c\nindex 9afa842da2..cbbfc8f1dc 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -415,7 +415,7 @@ int finalize_object_file_flags(struct repository *repo,\n retry:\n \tret = 0;\n \n-\tif (object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n+\tif (repo_config_values(repo)->object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n \t\tgoto try_rename;\n \telse if (link(tmpfile, filename))\n \t\tret = errno;\n-- \n2.43.0\n\n"},{"id":"547902","messageId":"DJWNK8BOFIAW.3VVAROHMKVDWE@gmail.com","threadId":"65873","inReplyTo":"20260712111734.1073514-5-cat@malon.dev","subject":"Re: [PATCH v10 4/9] environment: move pager_program into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-12T14:12:29Z","receivedAt":"2026-07-12T14:12:32Z","isPatch":true,"body":"On Sun Jul 12, 2026 at 1:17 PM CEST, Tian Yuchen wrote:\n> The 'pager_program' variable is currently defined as a file-scoped\n> static string in pager.c. Move it into 'struct repo_config_values'.\n>\n> The configuration parsing logic remains strictly within pager.c to\n> respect subsystem boundaries. The read/write operations are simply\n> redirected to the repository-specific structure using\n> 'repo_config_values()'.\n>\n> Similar to the recent editor_program migration, no standalone getter\n> is introduced to keep the code minimal. The dynamically allocated\n> memory is now managed by 'repo_config_values_clear()'.\n>\n> On top of that, fix a memory leak in pager.c while we are at it.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  environment.c |  2 ++\n>  environment.h |  1 +\n>  pager.c       | 26 +++++++++++++++++---------\n>  3 files changed, 20 insertions(+), 9 deletions(-)\n>\n> diff --git a/environment.c b/environment.c\n> index a65d575af4..975c9cb9eb 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -725,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  \tcfg->attributes_file = NULL;\n>  \tcfg->excludes_file = NULL;\n>  \tcfg->editor_program = NULL;\n> +\tcfg->pager_program = NULL;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> @@ -742,4 +743,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n>  \tFREE_AND_NULL(cfg->attributes_file);\n>  \tFREE_AND_NULL(cfg->excludes_file);\n>  \tFREE_AND_NULL(cfg->editor_program);\n> +\tFREE_AND_NULL(cfg->pager_program);\n>  }\n> diff --git a/environment.h b/environment.h\n> index 8178ebab76..39b6691b47 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -92,6 +92,7 @@ struct repo_config_values {\n>  \tchar *attributes_file;\n>  \tchar *excludes_file;\n>  \tchar *editor_program;\n> +\tchar *pager_program;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n> diff --git a/pager.c b/pager.c\n> index 35b210e048..bc55546670 100644\n> --- a/pager.c\n> +++ b/pager.c\n> @@ -5,6 +5,8 @@\n>  #include \"run-command.h\"\n>  #include \"sigchain.h\"\n>  #include \"alias.h\"\n> +#include \"repository.h\"\n> +#include \"environment.h\"\n>\n>  int pager_use_color = 1;\n>\n> @@ -13,7 +15,6 @@ int pager_use_color = 1;\n>  #endif\n>\n>  static struct child_process pager_process;\n> -static char *pager_program;\n>  static int old_fd1 = -1, old_fd2 = -1;\n>\n>  /* Is the value coming back from term_columns() just a guess? */\n> @@ -75,10 +76,15 @@ static void wait_for_pager_signal(int signo)\n>\n>  static int core_pager_config(const char *var, const char *value,\n>  \t\t\t     const struct config_context *ctx UNUSED,\n> -\t\t\t     void *data UNUSED)\n> +\t\t\t     void *data)\n\nCould this change behaviour that a caller expects?\n\n(looking at the hunk below) we are now using repo_config_values() which\ncontains the condition 'repo != the_repository'. This means that if there\nis a caller that sends anything but the_repository, it will BUG() out.\n\nBefore this patch it would have worked, it worked because callers were\nsending the correct repository. Now we enforce it.\n\nIf we check the callers we can see that everyone sends the_repository,\nso this new assert is fine and prevents sending submodules by mistake.\n\nMakes sense.\n\nI think it's worth mentioning that on the commit body/function.\n\n>  {\n> -\tif (!strcmp(var, \"core.pager\"))\n> -\t\treturn git_config_string(&pager_program, var, value);\n> +\tstruct repository *r = data;\n> +\n> +\tif (!strcmp(var, \"core.pager\")) {\n> +\t\tFREE_AND_NULL(repo_config_values(r)->pager_program);\n> +\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n> +\t}\n\nOk. Now that pager_program is not file-scoped we drop the UNUSED and\npager_program now lives in the per-repo field.\nThen we change the address where ->pager_program (which we access through\nrepo_config_values()) points to.\n\nFREE_AND_NULL() is new, before this patch it must have been leaking,\ngood job.\n\nSimilar to previous patches, let's change the pattern to only call\nrepo_config_values() once and use the pointer it returns.\n\n> +\n>  \treturn 0;\n>  }\n>\n> @@ -91,10 +97,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n>\n>  \tpager = getenv(\"GIT_PAGER\");\n>  \tif (!pager) {\n> -\t\tif (!pager_program)\n> +\t\tif (!repo_config_values(r)->pager_program)\n>  \t\t\tread_early_config(r,\n> -\t\t\t\t\t  core_pager_config, NULL);\n> -\t\tpager = pager_program;\n> +\t\t\t\t\t  core_pager_config, r);\n> +\t\tpager = repo_config_values(r)->pager_program;\n\nSame as above, let's call repo_config_values() once.\n\n>  \t}\n>  \tif (!pager)\n>  \t\tpager = getenv(\"PAGER\");\n> @@ -302,7 +308,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n>\n>  \tread_early_config(r, pager_command_config, &data);\n>\n> -\tif (data.value)\n> -\t\tpager_program = data.value;\n> +\tif (data.value) {\n> +\t\tfree(repo_config_values(r)->pager_program);\n> +\t\trepo_config_values(r)->pager_program = data.value;\n\nSame pattern. This also frees, but the log says \"a\" memory leak is fixed\nin this patch, should we change it to two?\n\n> +\t}\n>  \treturn data.want;\n>  }\n\nI peeked at later patches of this series and the multiple calls of\nrepo_config_values() pattern keeps appearing.\nI haven't finished reviewing 5-9. If you reroll before I get to those\npatches, it may be worth fixing this pattern across the whole series.\n\nRegards,\nPablo\n"},{"id":"547903","messageId":"DJWNYXB8MWLC.2PYST8ANV51MB@gmail.com","threadId":"65873","inReplyTo":"20260712111734.1073514-6-cat@malon.dev","subject":"Re: [PATCH v10 5/9] environment: move askpass_program into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-12T14:31:41Z","receivedAt":"2026-07-12T14:31:46Z","isPatch":true,"body":"On Sun Jul 12, 2026 at 1:17 PM CEST, Tian Yuchen wrote:\n> The global variable 'askpass_program' stores the path to the program\n> used to prompt the user for credentials. Move it into repo_config_values\n> to continue the libification effort.\n>\n> While it is uncommon for a single process to require different askpass\n> programs for different repositories, maintaining this value as a mutable\n> global string is a blocker for libification. Global heap-allocated\n> strings introduce thread-safety issues in a multi-repo environment.\n>\n> Move 'askpass_program' into 'struct repo_config_values' to eliminate\n> this global state. The memory is now safely managed and freed via\n> 'repo_config_values_clear()'.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  environment.c | 6 ++++--\n>  environment.h | 1 +\n>  prompt.c      | 3 ++-\n>  3 files changed, 7 insertions(+), 3 deletions(-)\n>\n> diff --git a/environment.c b/environment.c\n> index 975c9cb9eb..1a26c9c6d6 100644\n> --- a/environment.c\n> +++ b/environment.c\n\nI think that the drop of the global variable is missing.\n\n> @@ -464,8 +464,8 @@ int git_default_core_config(const char *var, const char *value,\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.askpass\")) {\n> -\t\tFREE_AND_NULL(askpass_program);\n> -\t\treturn git_config_string(&askpass_program, var, value);\n> +\t\tFREE_AND_NULL(cfg->askpass_program);\n> +\t\treturn git_config_string(&cfg->askpass_program, var, value);\n>  \t}\n>\n>  \tif (!strcmp(var, \"core.excludesfile\")) {\n> @@ -726,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  \tcfg->excludes_file = NULL;\n>  \tcfg->editor_program = NULL;\n>  \tcfg->pager_program = NULL;\n> +\tcfg->askpass_program = NULL;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> @@ -744,4 +745,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n>  \tFREE_AND_NULL(cfg->excludes_file);\n>  \tFREE_AND_NULL(cfg->editor_program);\n>  \tFREE_AND_NULL(cfg->pager_program);\n> +\tFREE_AND_NULL(cfg->askpass_program);\n>  }\n> diff --git a/environment.h b/environment.h\n> index 39b6691b47..a2e9def89d 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -93,6 +93,7 @@ struct repo_config_values {\n>  \tchar *excludes_file;\n>  \tchar *editor_program;\n>  \tchar *pager_program;\n> +\tchar *askpass_program;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n> diff --git a/prompt.c b/prompt.c\n> index 706fba2a50..d8d74c7e37 100644\n> --- a/prompt.c\n> +++ b/prompt.c\n> @@ -3,6 +3,7 @@\n>  #include \"git-compat-util.h\"\n>  #include \"parse.h\"\n>  #include \"environment.h\"\n> +#include \"repository.h\"\n>  #include \"run-command.h\"\n>  #include \"strbuf.h\"\n>  #include \"prompt.h\"\n> @@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)\n>\n>  \t\taskpass = getenv(\"GIT_ASKPASS\");\n>  \t\tif (!askpass)\n> -\t\t\taskpass = askpass_program;\n> +\t\t\taskpass = repo_config_values(the_repository)->askpass_program;\n>  \t\tif (!askpass)\n>  \t\t\taskpass = getenv(\"SSH_ASKPASS\");\n>  \t\tif (askpass && *askpass)\n\nThe rest LGTM.\n\nRegards,\nPablo\n"},{"id":"547905","messageId":"DJWOO33P3Y4G.3QMT23XDJFIFV@gmail.com","threadId":"65873","inReplyTo":"20260712111734.1073514-7-cat@malon.dev","subject":"Re: [PATCH v10 6/9] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-12T15:04:33Z","receivedAt":"2026-07-12T15:04:36Z","isPatch":true,"body":"On Sun Jul 12, 2026 at 1:17 PM CEST, Tian Yuchen wrote:\n> The global variables 'apply_default_whitespace' and\n> 'apply_default_ignorewhitespace' are used to store the default\n> whitespace configuration for 'git apply'. Move these variables\n> into 'struct repo_config_values' to continue the libification\n> effort.\n>\n> Dynamically allocated strings fetched via 'repo_config_get_string()'\n> are now tracked per-repository and safely freed in\n> 'repo_config_values_clear()'.\n>\n> As part of this transition, update 'git_apply_config()' to accept a\n> 'struct repository *' argument rather than relying on the\n> 'the_repository' global.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  apply.c       | 20 ++++++++++++--------\n>  environment.c |  6 ++++--\n>  environment.h |  4 ++--\n>  3 files changed, 18 insertions(+), 12 deletions(-)\n>\n> diff --git a/apply.c b/apply.c\n> index 249248d4f2..66db9b7678 100644\n> --- a/apply.c\n> +++ b/apply.c\n> @@ -47,11 +47,13 @@ struct gitdiff_data {\n>  \tint p_value;\n>  };\n>\n> -static void git_apply_config(void)\n> +static void git_apply_config(struct repository *repo)\n>  {\n> -\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n> -\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n> -\trepo_config(the_repository, git_xmerge_config, NULL);\n> +\trepo_config_get_string(repo, \"apply.whitespace\",\n> +\t\t\t       &repo_config_values(repo)->apply_default_whitespace);\n> +\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n> +\t\t\t       &repo_config_values(repo)->apply_default_ignorewhitespace);\n\nSame pattern, let's call repo_config_values() once.\nAlso, similar to the previous patches, shouldn't be here a\nFREE_AND_NULL() before each repo_config_get_string() call?\n\n> +\trepo_config(repo, git_xmerge_config, NULL);\n>  }\n>\n>  static int parse_whitespace_option(struct apply_state *state, const char *option)\n> @@ -126,10 +128,12 @@ int init_apply_state(struct apply_state *state,\n>  \tstrset_init(&state->kept_symlinks);\n>  \tstrbuf_init(&state->root, 0);\n>\n> -\tgit_apply_config();\n> -\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n> +\tgit_apply_config(repo);\n> +\tif (repo_config_values(repo)->apply_default_whitespace &&\n> +\t    parse_whitespace_option(state, repo_config_values(repo)->apply_default_whitespace))\n>  \t\treturn -1;\n> -\tif (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))\n> +\tif (repo_config_values(repo)->apply_default_ignorewhitespace &&\n> +\t    parse_ignorewhitespace_option(state, repo_config_values(repo)->apply_default_ignorewhitespace))\n>  \t\treturn -1;\n>  \treturn 0;\n>  }\n> @@ -192,7 +196,7 @@ int check_apply_state(struct apply_state *state, int force_apply)\n>\n>  static void set_default_whitespace_mode(struct apply_state *state)\n>  {\n> -\tif (!state->whitespace_option && !apply_default_whitespace)\n> +\tif (!state->whitespace_option && !repo_config_values(state->repo)->apply_default_whitespace)\n\nWe should extract cfg from repo_config_values() to avoid this overly\nlong line.\n\n>  \t\tstate->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);\n>  }\n>\n> diff --git a/environment.c b/environment.c\n> index 1a26c9c6d6..41ba013c86 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -49,8 +49,6 @@ int assume_unchanged;\n>  int is_bare_repository_cfg = -1; /* unspecified */\n>  char *git_commit_encoding;\n>  char *git_log_output_encoding;\n> -char *apply_default_whitespace;\n> -char *apply_default_ignorewhitespace;\n>  int fsync_object_files = -1;\n>  int use_fsync = -1;\n>  enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n> @@ -727,6 +725,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  \tcfg->editor_program = NULL;\n>  \tcfg->pager_program = NULL;\n>  \tcfg->askpass_program = NULL;\n> +\tcfg->apply_default_whitespace = NULL;\n> +\tcfg->apply_default_ignorewhitespace = NULL;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> @@ -746,4 +746,6 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n>  \tFREE_AND_NULL(cfg->editor_program);\n>  \tFREE_AND_NULL(cfg->pager_program);\n>  \tFREE_AND_NULL(cfg->askpass_program);\n> +\tFREE_AND_NULL(cfg->apply_default_whitespace);\n> +\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n>  }\n> diff --git a/environment.h b/environment.h\n> index a2e9def89d..553f87adee 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -94,6 +94,8 @@ struct repo_config_values {\n>  \tchar *editor_program;\n>  \tchar *pager_program;\n>  \tchar *askpass_program;\n> +\tchar *apply_default_whitespace;\n> +\tchar *apply_default_ignorewhitespace;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n> @@ -182,8 +184,6 @@ extern int has_symlinks;\n>  extern int minimum_abbrev, default_abbrev;\n>  extern int ignore_case;\n>  extern int assume_unchanged;\n> -extern char *apply_default_whitespace;\n> -extern char *apply_default_ignorewhitespace;\n>  extern unsigned long pack_size_limit_cfg;\n>\n>  extern int protect_hfs;\n\nThe rest LGTM.\n\nRegards,\nPablo\n"},{"id":"547906","messageId":"xmqqy0fg43vf.fsf@gitster.g","threadId":"65873","inReplyTo":"20260712111734.1073514-5-cat@malon.dev","subject":"Re: [PATCH v10 4/9] environment: move pager_program into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-12T15:36:04Z","receivedAt":"2026-07-12T15:36:07Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> The 'pager_program' variable is currently defined as a file-scoped\n> static string in pager.c. Move it into 'struct repo_config_values'.\n>\n> The configuration parsing logic remains strictly within pager.c to\n> respect subsystem boundaries. The read/write operations are simply\n> redirected to the repository-specific structure using\n> 'repo_config_values()'.\n\nBy redirecting to repo_config_values(r), we now enforce that the\npassed repository must be 'the_repository' (due to the assertion in\nrepo_config_values()).  All current callers of git_pager() and\ncheck_pager_config() indeed pass 'the_repository', so this new\nenforcement does not harm them.  However, it paves the way to later\nlift the assertion and allow us to configure different pagers for\ndifferent repositories, which is a welcome improvement.\n\n>  static int core_pager_config(const char *var, const char *value,\n>  \t\t\t     const struct config_context *ctx UNUSED,\n> -\t\t\t     void *data UNUSED)\n> +\t\t\t     void *data)\n>  {\n> -\tif (!strcmp(var, \"core.pager\"))\n> -\t\treturn git_config_string(&pager_program, var, value);\n> +\tstruct repository *r = data;\n> +\n> +\tif (!strcmp(var, \"core.pager\")) {\n> +\t\tFREE_AND_NULL(repo_config_values(r)->pager_program);\n> +\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n> +\t}\n\nIt may be just me, but I would have preferred to see this written\nmore like\n\n\n\tif (!strcmp(var, \"core.pager\")) {\n\t\tstruct repo_config_values *values = repo_config_values(r);\n\n\t\tFREE_AND_NULL(values->pager_program);\n\t\treturn git_config_string(&values->pager_program, var, value);\n\t}\n\nwhich will make it easier to see that we are freeing the same thing\nimmediately before we overwrite it.  It also shortens the lines.\nFor a temporary variable with a very short scope like this one that\nis introduced solely for readability, it is OK to use even shorter\nname like 'v' if you want to ('r' certainly has a much longer\nlifespan that it, and I would probably have preferred to see it\ncalled 'repo').\n\n    Side note: we might want to give a hint in the coding guidelines\n    document that a variable with larger lifespan should get longer\n    names, or something.\n\n>  \treturn 0;\n>  }\n>  \n> @@ -91,10 +97,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n>  \n>  \tpager = getenv(\"GIT_PAGER\");\n>  \tif (!pager) {\n> -\t\tif (!pager_program)\n> +\t\tif (!repo_config_values(r)->pager_program)\n>  \t\t\tread_early_config(r,\n> -\t\t\t\t\t  core_pager_config, NULL);\n> -\t\tpager = pager_program;\n> +\t\t\t\t\t  core_pager_config, r);\n> +\t\tpager = repo_config_values(r)->pager_program;\n>  \t}\n\nSame here.\n\n>  \tif (!pager)\n>  \t\tpager = getenv(\"PAGER\");\n> @@ -302,7 +308,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n>  \n>  \tread_early_config(r, pager_command_config, &data);\n>  \n> -\tif (data.value)\n> -\t\tpager_program = data.value;\n> +\tif (data.value) {\n> +\t\tfree(repo_config_values(r)->pager_program);\n> +\t\trepo_config_values(r)->pager_program = data.value;\n> +\t}\n\nSame here.\n"},{"id":"547907","messageId":"xmqqfr1o43d5.fsf@gitster.g","threadId":"65873","inReplyTo":"20260712111734.1073514-6-cat@malon.dev","subject":"Re: [PATCH v10 5/9] environment: move askpass_program into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-12T15:47:02Z","receivedAt":"2026-07-12T15:47:04Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n>  environment.c | 6 ++++--\n>  environment.h | 1 +\n>  prompt.c      | 3 ++-\n>  3 files changed, 7 insertions(+), 3 deletions(-)\n>\n> diff --git a/environment.c b/environment.c\n> index 975c9cb9eb..1a26c9c6d6 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -464,8 +464,8 @@ int git_default_core_config(const char *var, const char *value,\n>  \t}\n>  \n>  \tif (!strcmp(var, \"core.askpass\")) {\n> -\t\tFREE_AND_NULL(askpass_program);\n> -\t\treturn git_config_string(&askpass_program, var, value);\n> +\t\tFREE_AND_NULL(cfg->askpass_program);\n> +\t\treturn git_config_string(&cfg->askpass_program, var, value);\n>  \t}\n> @@ -726,6 +726,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  \tcfg->excludes_file = NULL;\n>  \tcfg->editor_program = NULL;\n>  \tcfg->pager_program = NULL;\n> +\tcfg->askpass_program = NULL;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> @@ -744,4 +745,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n>  \tFREE_AND_NULL(cfg->excludes_file);\n>  \tFREE_AND_NULL(cfg->editor_program);\n>  \tFREE_AND_NULL(cfg->pager_program);\n> +\tFREE_AND_NULL(cfg->askpass_program);\n>  }\n\nThe 'askpass_program' global variable has been removed.  Instead, \nthe member in repo_config_values is correctly initialized to NULL \nand properly cleaned up when finished.\n\n> diff --git a/environment.h b/environment.h\n> index 39b6691b47..a2e9def89d 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -93,6 +93,7 @@ struct repo_config_values {\n>  \tchar *excludes_file;\n>  \tchar *editor_program;\n>  \tchar *pager_program;\n> +\tchar *askpass_program;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n\nWait, wasn't there an extern declaration for that global variable in \na header file somewhere?  There must also be an actual definition \nfor it.  Both should be removed to ensure no one accesses a stale \nvariable; doing so allows the compiler to help you spot any \nleftover users.\n\nThanks.\n"},{"id":"547908","messageId":"DJWPMI6GXV28.1LRVLX04M49GC@gmail.com","threadId":"65873","inReplyTo":"20260712111734.1073514-8-cat@malon.dev","subject":"Re: [PATCH v10 7/9] environment: move push_default into repo_config_values","fromName":"Pablo Sabater","fromEmail":"pabloosabaterr@gmail.com","sentAt":"2026-07-12T15:49:30Z","receivedAt":"2026-07-12T15:49:33Z","isPatch":true,"body":"On Sun Jul 12, 2026 at 1:17 PM CEST, Tian Yuchen wrote:\n> The global variable 'push_default' specifies the default behavior of\n> 'git push' when no explicit refspec is provided. Move 'push_default'\n> into 'struct repo_config_values' to continue the libification effort.\n>\n> While 'enum push_default_type' ideally belongs in 'remote.h', moving it\n> there introduces a circular dependency chain:\n>\n>   remote.h -> hash.h -> repository.h -> environment.h.\n>\n> Therefore, the enum definition is kept in 'environment.h' just above\n> 'struct repo_config_values' with a NEEDSWORK comment for future cleanup.\n>\n> Modify the configuration parsing in environment.c to update the\n> per-repository structure directly, and update caller across the\n> codebase to access the value via 'repo_config_values()'.\n>\n> Mentored-by: Christian Couder <christian.couder@gmail.com>\n> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n> Signed-off-by: Tian Yuchen <cat@malon.dev>\n> ---\n>  builtin/push.c |  8 ++++----\n>  environment.c  | 16 +++++++++-------\n>  environment.h  | 26 ++++++++++++++++----------\n>  remote.c       |  2 +-\n>  4 files changed, 30 insertions(+), 22 deletions(-)\n>\n> diff --git a/builtin/push.c b/builtin/push.c\n> index 6021b71d66..6dc3224b60 100644\n> --- a/builtin/push.c\n> +++ b/builtin/push.c\n> @@ -88,7 +88,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,\n>  \t\t}\n>  \t}\n>\n> -\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n> +\tif (repo_config_values(the_repository)->push_default == PUSH_DEFAULT_UPSTREAM &&\n\nCan we extract cfg from repo_config_values() to shorten this line?\nIf we look at the hunk below, we can see this pattern. let's do the\nsame.\n\n>  \t    skip_prefix(matched->name, \"refs/heads/\", &branch_name)) {\n>  \t\tstruct branch *branch = branch_get(branch_name);\n>  \t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n> @@ -160,7 +160,7 @@ static NORETURN void die_push_simple(struct branch *branch,\n>  \t * Don't show advice for people who explicitly set\n>  \t * push.default.\n>  \t */\n> -\tif (push_default == PUSH_DEFAULT_UNSPECIFIED)\n> +\tif (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)\n>  \t\tadvice_pushdefault_maybe = _(\"\\n\"\n>  \t\t\t\t \"To choose either option permanently, \"\n>  \t\t\t\t \"see push.default in 'git help config'.\\n\");\n> @@ -232,7 +232,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n>  \tconst char *dst;\n>  \tint same_remote;\n>\n> -\tswitch (push_default) {\n> +\tswitch (repo_config_values(the_repository)->push_default) {\n>  \tcase PUSH_DEFAULT_MATCHING:\n>  \t\trefspec_append(&rs, \":\");\n>  \t\treturn;\n> @@ -252,7 +252,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n>  \tdst = branch->refname;\n>  \tsame_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));\n>\n> -\tswitch (push_default) {\n> +\tswitch (repo_config_values(the_repository)->push_default) {\n>  \tdefault:\n>  \tcase PUSH_DEFAULT_UNSPECIFIED:\n>  \tcase PUSH_DEFAULT_SIMPLE:\n> diff --git a/environment.c b/environment.c\n> index 41ba013c86..0080012f31 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -59,7 +59,6 @@ enum eol core_eol = EOL_UNSET;\n>  int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n>  char *check_roundtrip_encoding;\n>  enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n> -enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n>  #ifndef OBJECT_CREATION_MODE\n>  #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n>  #endif\n> @@ -621,21 +620,23 @@ static int git_default_branch_config(const char *var, const char *value)\n>\n>  static int git_default_push_config(const char *var, const char *value)\n>  {\n> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n> +\n>  \tif (!strcmp(var, \"push.default\")) {\n>  \t\tif (!value)\n>  \t\t\treturn config_error_nonbool(var);\n>  \t\telse if (!strcmp(value, \"nothing\"))\n> -\t\t\tpush_default = PUSH_DEFAULT_NOTHING;\n> +\t\t\tcfg->push_default = PUSH_DEFAULT_NOTHING;\n>  \t\telse if (!strcmp(value, \"matching\"))\n> -\t\t\tpush_default = PUSH_DEFAULT_MATCHING;\n> +\t\t\tcfg->push_default = PUSH_DEFAULT_MATCHING;\n>  \t\telse if (!strcmp(value, \"simple\"))\n> -\t\t\tpush_default = PUSH_DEFAULT_SIMPLE;\n> +\t\t\tcfg->push_default = PUSH_DEFAULT_SIMPLE;\n>  \t\telse if (!strcmp(value, \"upstream\"))\n> -\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n> +\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n>  \t\telse if (!strcmp(value, \"tracking\")) /* deprecated */\n> -\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n> +\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n>  \t\telse if (!strcmp(value, \"current\"))\n> -\t\t\tpush_default = PUSH_DEFAULT_CURRENT;\n> +\t\t\tcfg->push_default = PUSH_DEFAULT_CURRENT;\n>  \t\telse {\n>  \t\t\terror(_(\"malformed value for %s: %s\"), var, value);\n>  \t\t\treturn error(_(\"must be one of nothing, matching, simple, \"\n> @@ -727,6 +728,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  \tcfg->askpass_program = NULL;\n>  \tcfg->apply_default_whitespace = NULL;\n>  \tcfg->apply_default_ignorewhitespace = NULL;\n> +\tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n>  \tcfg->apply_sparse_checkout = 0;\n>  \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>  \tcfg->trust_ctime = 1;\n> diff --git a/environment.h b/environment.h\n> index 553f87adee..6a5c8bd06f 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -87,6 +87,21 @@ extern const char * const local_repo_env[];\n>  struct strvec;\n>\n>  struct repository;\n> +\n> +/*\n> + * NEEDSWORK: It would be better if these definitions could be moved to\n> + * other more specific files, but care is needed to avoid circular\n> + * inclusion issues.\n> + */\n> +enum push_default_type {\n> +\tPUSH_DEFAULT_NOTHING = 0,\n> +\tPUSH_DEFAULT_MATCHING,\n> +\tPUSH_DEFAULT_SIMPLE,\n> +\tPUSH_DEFAULT_UPSTREAM,\n> +\tPUSH_DEFAULT_CURRENT,\n> +\tPUSH_DEFAULT_UNSPECIFIED\n> +};\n> +\n>  struct repo_config_values {\n>  \t/* section \"core\" config values */\n>  \tchar *attributes_file;\n> @@ -96,6 +111,7 @@ struct repo_config_values {\n>  \tchar *askpass_program;\n>  \tchar *apply_default_whitespace;\n>  \tchar *apply_default_ignorewhitespace;\n> +\tenum push_default_type push_default;\n>  \tint apply_sparse_checkout;\n>  \tint trust_ctime;\n>  \tint check_stat;\n> @@ -197,16 +213,6 @@ enum rebase_setup_type {\n>  };\n>  extern enum rebase_setup_type autorebase;\n>\n> -enum push_default_type {\n> -\tPUSH_DEFAULT_NOTHING = 0,\n> -\tPUSH_DEFAULT_MATCHING,\n> -\tPUSH_DEFAULT_SIMPLE,\n> -\tPUSH_DEFAULT_UPSTREAM,\n> -\tPUSH_DEFAULT_CURRENT,\n> -\tPUSH_DEFAULT_UNSPECIFIED\n> -};\n> -extern enum push_default_type push_default;\n> -\n>  enum object_creation_mode {\n>  \tOBJECT_CREATION_USES_HARDLINKS = 0,\n>  \tOBJECT_CREATION_USES_RENAMES = 1\n> diff --git a/remote.c b/remote.c\n> index 00723b385e..d48c01d375 100644\n> --- a/remote.c\n> +++ b/remote.c\n> @@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo,\n>  \tif (remote->mirror)\n>  \t\treturn tracking_for_push_dest(remote, branch->refname, err);\n>\n> -\tswitch (push_default) {\n> +\tswitch (repo_config_values(repo)->push_default) {\n>  \tcase PUSH_DEFAULT_NOTHING:\n>  \t\treturn error_buf(err, _(\"push has no destination (push.default is 'nothing')\"));\n>\n\nI haven't checked how doable fixing the dependency cycle of the\nNEEDSWORK is.\n\nSimilar to other patches where we add repo_config_values() to functions\nthat didn't have it before. We need to check that there's no caller that\ncould pass a repository different from the_repository. I haven't checked\nit this time.\n\nRegards,\nPablo\n"},{"id":"547910","messageId":"79e2b5b4-fc9e-4ca6-a4ff-dff93a3c64bc@malon.dev","threadId":"65873","inReplyTo":"DJWNK8BOFIAW.3VVAROHMKVDWE@gmail.com","subject":"Re: [PATCH v10 4/9] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T16:54:58Z","receivedAt":"2026-07-12T16:55:08Z","isPatch":true,"body":"On 7/12/26 22:12, Pablo Sabater wrote:\n> On Sun Jul 12, 2026 at 1:17 PM CEST, Tian Yuchen wrote:\n>> The 'pager_program' variable is currently defined as a file-scoped\n>> static string in pager.c. Move it into 'struct repo_config_values'.\n>>\n>> The configuration parsing logic remains strictly within pager.c to\n>> respect subsystem boundaries. The read/write operations are simply\n>> redirected to the repository-specific structure using\n>> 'repo_config_values()'.\n>>\n>> Similar to the recent editor_program migration, no standalone getter\n>> is introduced to keep the code minimal. The dynamically allocated\n>> memory is now managed by 'repo_config_values_clear()'.\n>>\n>> On top of that, fix a memory leak in pager.c while we are at it.\n>>\n>> Mentored-by: Christian Couder <christian.couder@gmail.com>\n>> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\n>> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com>\n>> Signed-off-by: Tian Yuchen <cat@malon.dev>\n>> ---\n>>   environment.c |  2 ++\n>>   environment.h |  1 +\n>>   pager.c       | 26 +++++++++++++++++---------\n>>   3 files changed, 20 insertions(+), 9 deletions(-)\n>>\n>> diff --git a/environment.c b/environment.c\n>> index a65d575af4..975c9cb9eb 100644\n>> --- a/environment.c\n>> +++ b/environment.c\n>> @@ -725,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>>   \tcfg->attributes_file = NULL;\n>>   \tcfg->excludes_file = NULL;\n>>   \tcfg->editor_program = NULL;\n>> +\tcfg->pager_program = NULL;\n>>   \tcfg->apply_sparse_checkout = 0;\n>>   \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>>   \tcfg->trust_ctime = 1;\n>> @@ -742,4 +743,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n>>   \tFREE_AND_NULL(cfg->attributes_file);\n>>   \tFREE_AND_NULL(cfg->excludes_file);\n>>   \tFREE_AND_NULL(cfg->editor_program);\n>> +\tFREE_AND_NULL(cfg->pager_program);\n>>   }\n>> diff --git a/environment.h b/environment.h\n>> index 8178ebab76..39b6691b47 100644\n>> --- a/environment.h\n>> +++ b/environment.h\n>> @@ -92,6 +92,7 @@ struct repo_config_values {\n>>   \tchar *attributes_file;\n>>   \tchar *excludes_file;\n>>   \tchar *editor_program;\n>> +\tchar *pager_program;\n>>   \tint apply_sparse_checkout;\n>>   \tint trust_ctime;\n>>   \tint check_stat;\n>> diff --git a/pager.c b/pager.c\n>> index 35b210e048..bc55546670 100644\n>> --- a/pager.c\n>> +++ b/pager.c\n>> @@ -5,6 +5,8 @@\n>>   #include \"run-command.h\"\n>>   #include \"sigchain.h\"\n>>   #include \"alias.h\"\n>> +#include \"repository.h\"\n>> +#include \"environment.h\"\n>>\n>>   int pager_use_color = 1;\n>>\n>> @@ -13,7 +15,6 @@ int pager_use_color = 1;\n>>   #endif\n>>\n>>   static struct child_process pager_process;\n>> -static char *pager_program;\n>>   static int old_fd1 = -1, old_fd2 = -1;\n>>\n>>   /* Is the value coming back from term_columns() just a guess? */\n>> @@ -75,10 +76,15 @@ static void wait_for_pager_signal(int signo)\n>>\n>>   static int core_pager_config(const char *var, const char *value,\n>>   \t\t\t     const struct config_context *ctx UNUSED,\n>> -\t\t\t     void *data UNUSED)\n>> +\t\t\t     void *data)\n> \n> Could this change behaviour that a caller expects?\n> \n> (looking at the hunk below) we are now using repo_config_values() which\n> contains the condition 'repo != the_repository'. This means that if there\n> is a caller that sends anything but the_repository, it will BUG() out.\n> \n> Before this patch it would have worked, it worked because callers were\n> sending the correct repository. Now we enforce it.\n> \n> If we check the callers we can see that everyone sends the_repository,\n> so this new assert is fine and prevents sending submodules by mistake.\n> \n> Makes sense.\n> \n\n;)\n\n\n> I think it's worth mentioning that on the commit body/function.\n> \n\nOkay, will add a line to briefly explain this. Something like what Junio \nsaid:\n\n\tAll current callers of git_pager() and\n\tcheck_pager_config() indeed pass 'the_repository', so this new\n\tenforcement does not harm them.\n\n>>   {\n>> -\tif (!strcmp(var, \"core.pager\"))\n>> -\t\treturn git_config_string(&pager_program, var, value);\n>> +\tstruct repository *r = data;\n>> +\n>> +\tif (!strcmp(var, \"core.pager\")) {\n>> +\t\tFREE_AND_NULL(repo_config_values(r)->pager_program);\n>> +\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n>> +\t}\n> \n> Ok. Now that pager_program is not file-scoped we drop the UNUSED and\n> pager_program now lives in the per-repo field.\n> Then we change the address where ->pager_program (which we access through\n> repo_config_values()) points to.\n> \n> FREE_AND_NULL() is new, before this patch it must have been leaking,\n> good job.\n> \n> Similar to previous patches, let's change the pattern to only call\n> repo_config_values() once and use the pointer it returns.\n> \n>> +\n>>   \treturn 0;\n>>   }\n>>\n>> @@ -91,10 +97,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n>>\n>>   \tpager = getenv(\"GIT_PAGER\");\n>>   \tif (!pager) {\n>> -\t\tif (!pager_program)\n>> +\t\tif (!repo_config_values(r)->pager_program)\n>>   \t\t\tread_early_config(r,\n>> -\t\t\t\t\t  core_pager_config, NULL);\n>> -\t\tpager = pager_program;\n>> +\t\t\t\t\t  core_pager_config, r);\n>> +\t\tpager = repo_config_values(r)->pager_program;\n> \n> Same as above, let's call repo_config_values() once.\n> \n>>   \t}\n>>   \tif (!pager)\n>>   \t\tpager = getenv(\"PAGER\");\n>> @@ -302,7 +308,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n>>\n>>   \tread_early_config(r, pager_command_config, &data);\n>>\n>> -\tif (data.value)\n>> -\t\tpager_program = data.value;\n>> +\tif (data.value) {\n>> +\t\tfree(repo_config_values(r)->pager_program);\n>> +\t\trepo_config_values(r)->pager_program = data.value;\n> \n> Same pattern. This also frees, but the log says \"a\" memory leak is fixed\n> in this patch, should we change it to two?\n\nNice catch.\n\n> \n>> +\t}\n>>   \treturn data.want;\n>>   }\n> \n> I peeked at later patches of this series and the multiple calls of\n> repo_config_values() pattern keeps appearing.\n> I haven't finished reviewing 5-9. If you reroll before I get to those\n> patches, it may be worth fixing this pattern across the whole series.\n> \n> Regards,\n> Pablo\n\nFor those cases where multiple calls to repo_config_values() appear in a \nsingle function body, I will fix such pattern by using cfg.\n\nThanks, yuchen\n"},{"id":"547911","messageId":"2a39cee9-1082-48aa-b42e-e12c34fa0e29@malon.dev","threadId":"65873","inReplyTo":"xmqqy0fg43vf.fsf@gitster.g","subject":"Re: [PATCH v10 4/9] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-12T16:58:51Z","receivedAt":"2026-07-12T16:59:01Z","isPatch":true,"body":"On 7/12/26 23:36, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> The 'pager_program' variable is currently defined as a file-scoped\n>> static string in pager.c. Move it into 'struct repo_config_values'.\n>>\n>> The configuration parsing logic remains strictly within pager.c to\n>> respect subsystem boundaries. The read/write operations are simply\n>> redirected to the repository-specific structure using\n>> 'repo_config_values()'.\n> \n> By redirecting to repo_config_values(r), we now enforce that the\n> passed repository must be 'the_repository' (due to the assertion in\n> repo_config_values()).  All current callers of git_pager() and\n> check_pager_config() indeed pass 'the_repository', so this new\n> enforcement does not harm them.  However, it paves the way to later\n> lift the assertion and allow us to configure different pagers for\n> different repositories, which is a welcome improvement.\n> \n\nExactly.\n\n>>   static int core_pager_config(const char *var, const char *value,\n>>   \t\t\t     const struct config_context *ctx UNUSED,\n>> -\t\t\t     void *data UNUSED)\n>> +\t\t\t     void *data)\n>>   {\n>> -\tif (!strcmp(var, \"core.pager\"))\n>> -\t\treturn git_config_string(&pager_program, var, value);\n>> +\tstruct repository *r = data;\n>> +\n>> +\tif (!strcmp(var, \"core.pager\")) {\n>> +\t\tFREE_AND_NULL(repo_config_values(r)->pager_program);\n>> +\t\treturn git_config_string(&repo_config_values(r)->pager_program, var, value);\n>> +\t}\n> \n> It may be just me, but I would have preferred to see this written\n> more like\n> \n> \n> \tif (!strcmp(var, \"core.pager\")) {\n> \t\tstruct repo_config_values *values = repo_config_values(r);\n> \n> \t\tFREE_AND_NULL(values->pager_program);\n> \t\treturn git_config_string(&values->pager_program, var, value);\n> \t}\n> \n\nI see. I think it's better to name the struct 'cfg' so that it is \nconsistent with what we did before.\n\n> which will make it easier to see that we are freeing the same thing\n> immediately before we overwrite it.  It also shortens the lines.\n> For a temporary variable with a very short scope like this one that\n> is introduced solely for readability, it is OK to use even shorter\n> name like 'v' if you want to ('r' certainly has a much longer\n> lifespan that it, and I would probably have preferred to see it\n> called 'repo').\n> \n>      Side note: we might want to give a hint in the coding guidelines\n>      document that a variable with larger lifespan should get longer\n>      names, or something.\n> \n>>   \treturn 0;\n>>   }\n>>   \n>> @@ -91,10 +97,10 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n>>   \n>>   \tpager = getenv(\"GIT_PAGER\");\n>>   \tif (!pager) {\n>> -\t\tif (!pager_program)\n>> +\t\tif (!repo_config_values(r)->pager_program)\n>>   \t\t\tread_early_config(r,\n>> -\t\t\t\t\t  core_pager_config, NULL);\n>> -\t\tpager = pager_program;\n>> +\t\t\t\t\t  core_pager_config, r);\n>> +\t\tpager = repo_config_values(r)->pager_program;\n>>   \t}\n> \n> Same here.\n> \n>>   \tif (!pager)\n>>   \t\tpager = getenv(\"PAGER\");\n>> @@ -302,7 +308,9 @@ int check_pager_config(struct repository *r, const char *cmd)\n>>   \n>>   \tread_early_config(r, pager_command_config, &data);\n>>   \n>> -\tif (data.value)\n>> -\t\tpager_program = data.value;\n>> +\tif (data.value) {\n>> +\t\tfree(repo_config_values(r)->pager_program);\n>> +\t\trepo_config_values(r)->pager_program = data.value;\n>> +\t}\n> \n> Same here.\n\nThanks, will change all these parts.\n\nRegard, yuchen\n"},{"id":"547930","messageId":"20260713035738.1606138-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v11 00/10] migrate more variables into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-13T03:57:28Z","receivedAt":"2026-07-13T03:57:47Z","isPatch":true,"body":"Hi everyone,\n\nThis patch series continues the ongoing libification effort by migrating\na batch of global configuration variables into struct repo_config_values.\n\nWhat does this series do:\n\ninfrastructure & strings (commits 1-6):\nIntroduce 'repo_config_values_clear()' to manage the lifecycle\nof heap-allocated configuration strings. This infrastructure is utilized\nto migrate string variables, including 'excludes_file', 'apply' whitespace\nconfigs, and external programs including 'editor', 'pager', 'askpass'.\n\nenums (commits 7-9):\nMigrate enumerations 'push_default', 'autorebase', and\n'object_creation_mode'. Care was taken to make these types available\nto the configuration structure without triggering circular header\ndependencies.\n\nedit comment (commit 10):\nAdjust the comment for config_values_private_ in repository.h.\n\nRFC:\n\nCommit 3~5. Is it really necessary to migrate _program variables?\nhttps://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n\nCommit 6~9. Previous related discussions on 'git_branch_track'.\nhttps://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n\nNote:\n\nSince a new getter 'repo_excludes_file()' is introduced, as previously\npromised, once it is finally merged into 'master', there will be a patch to\nupdate and squash the comments.\n\nSimilarly, I've noticed that the classification and sorting of variables in\n'repo_config_values' don't seem to be correct. There will also be a patch\nto fix this, and I think it will form a commit series along with the comment\npatch?\n\nChanges since v10:\n\n - use repo_config_values *cfg to avoid multiple calls to\n repo_config_values() and avoid overly long lines.\n\n - drop the extern declarations for askpass_program.\n\n - in the commit message of pager_program migration, mention that the\n new assertion is fine since current callers pass the_repository only. \n\n - add FREE_AND_NULL()s before repo_config_get_strings() calls.\n\n - create a new commit to adjust the comment for config_values_private_\n since it was no longer true.\n\nSpecial thanks to Pablo and Junio!\n\nTian Yuchen (10):\n  repository: introduce repo_config_values_clear()\n  environment: move excludes_file into repo_config_values\n  environment: move editor_program into repo_config_values\n  environment: move pager_program into repo_config_values\n  environment: move askpass_program into repo_config_values\n  environment: migrate apply_default_whitespace and\n    apply_default_ignorewhitespace\n  environment: move push_default into repo_config_values\n  environment: move autorebase into repo_config_values\n  environment: move object_creation_mode into repo_config_values\n  repository: adjust the comment of config_values_private_\n\n apply.c        | 28 ++++++++++++------\n branch.c       |  2 +-\n builtin/push.c | 10 ++++---\n dir.c          |  4 +--\n editor.c       |  4 +--\n environment.c  | 76 ++++++++++++++++++++++++++++++++-----------------\n environment.h  | 77 ++++++++++++++++++++++++++++++--------------------\n object-file.c  |  3 +-\n pager.c        | 32 +++++++++++++++------\n prompt.c       |  3 +-\n remote.c       |  2 +-\n repository.c   |  1 +\n repository.h   |  2 +-\n 13 files changed, 158 insertions(+), 86 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"547931","messageId":"20260713035738.1606138-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260713035738.1606138-1-cat@malon.dev","subject":"[PATCH v11 01/10] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-13T03:57:29Z","receivedAt":"2026-07-13T03:57:51Z","isPatch":true,"body":"As part of the ongoing libification effort, dynamically allocated\nglobal configuration variables are being moved into\n'struct repo_config_values'. To prevent memory leaks, we need a\ndestructor to free these heap-allocated variables when a repository\ninstance is torn down.\n\nIntroduce 'repo_config_values_clear()' in environment.c and invoke it\nfrom 'repo_clear()' in repository.c. As a starting point, update this\nnew function to handle the cleanup of 'attributes_file'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 5 +++++\n environment.h | 9 +++++++++\n repository.c  | 1 +\n 3 files changed, 15 insertions(+)\n\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..ae05f16d04 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -726,3 +726,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repo_config_values *cfg)\n+{\n+\tFREE_AND_NULL(cfg->attributes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..9169d7f62d 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n \n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repo_config_values *cfg);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..669e2d1200 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(&repo->config_values_private_);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"547932","messageId":"20260713035738.1606138-3-cat@malon.dev","threadId":"65873","inReplyTo":"20260713035738.1606138-1-cat@malon.dev","subject":"[PATCH v11 02/10] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-13T03:57:30Z","receivedAt":"2026-07-13T03:57:56Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL,\n'setup_standard_excludes()' in 'dir.c' forcefully evaluates and assigns\nthe XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. It is safely freed using the newly\nintroduced 'repo_config_values_clear()' function when the repository\nis torn down.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 17 ++++++++++++++---\n environment.h |  4 +++-\n 3 files changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex ae05f16d04..275931c213 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,16 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n+\n+\tif (!cfg->excludes_file)\n+\t\tcfg->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn cfg->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +470,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +724,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -730,4 +740,5 @@ void repo_config_values_init(struct repo_config_values *cfg)\n void repo_config_values_clear(struct repo_config_values *cfg)\n {\n \tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n }\ndiff --git a/environment.h b/environment.h\nindex 9169d7f62d..4776ccc657 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\n-- \n2.43.0\n\n"},{"id":"547933","messageId":"20260713035738.1606138-4-cat@malon.dev","threadId":"65873","inReplyTo":"20260713035738.1606138-1-cat@malon.dev","subject":"[PATCH v11 03/10] environment: move editor_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-13T03:57:31Z","receivedAt":"2026-07-13T03:57:57Z","isPatch":true,"body":"The global variable 'editor_program' holds the path to the user's\npreferred editor. Move 'editor_program' into\n'struct repo_config_values' to continue the libification effort.\n\nThere have been discussions on whether external programs like\neditors truly need to be configured on a per-repository basis within\nthe same process. While a single process might rarely invoke\ndifferent editors, this migration is necessary for two reasons:\n\n1. Developers frequently use different toolchains for different\n   projects. Per-repo configuration respects this.\n\n2. Moving this string into 'repo_config_values' eliminates mutable\n   global state. As the codebase moves toward becoming a long-running\n   processes, managing multiple repositories concurrently must\n   not overwrite each other's program configurations.\n\nNo standalone getter function is introduced. Callers directly access\nthe field via 'repo_config_values()'. Heap memory is safely reclaimed\nin 'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n editor.c      | 4 ++--\n environment.c | 7 ++++---\n environment.h | 2 +-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/editor.c b/editor.c\nindex fd174e6a03..0d1cb8768d 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -29,8 +29,8 @@ const char *git_editor(void)\n \tconst char *editor = getenv(\"GIT_EDITOR\");\n \tint terminal_is_dumb = is_terminal_dumb();\n \n-\tif (!editor && editor_program)\n-\t\teditor = editor_program;\n+\tif (!editor)\n+\t\teditor = repo_config_values(the_repository)->editor_program;\n \tif (!editor && !terminal_is_dumb)\n \t\teditor = getenv(\"VISUAL\");\n \tif (!editor)\ndiff --git a/environment.c b/environment.c\nindex 275931c213..a65d575af4 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *editor_program;\n char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n@@ -437,8 +436,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.editor\")) {\n-\t\tFREE_AND_NULL(editor_program);\n-\t\treturn git_config_string(&editor_program, var, value);\n+\t\tFREE_AND_NULL(cfg->editor_program);\n+\t\treturn git_config_string(&cfg->editor_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n@@ -725,6 +724,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n+\tcfg->editor_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -741,4 +741,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n {\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n+\tFREE_AND_NULL(cfg->editor_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 4776ccc657..8178ebab76 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -91,6 +91,7 @@ struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n \tchar *excludes_file;\n+\tchar *editor_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *editor_program;\n extern char *askpass_program;\n \n /*\n-- \n2.43.0\n\n"},{"id":"547934","messageId":"20260713035738.1606138-5-cat@malon.dev","threadId":"65873","inReplyTo":"20260713035738.1606138-1-cat@malon.dev","subject":"[PATCH v11 04/10] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-13T03:57:32Z","receivedAt":"2026-07-13T03:58:02Z","isPatch":true,"body":"The 'pager_program' variable is currently defined as a file-scoped\nstatic string in pager.c. Move it into 'struct repo_config_values'.\n\nThe configuration parsing logic remains strictly within pager.c to\nrespect subsystem boundaries. The read/write operations are simply\nredirected to the repository-specific structure using\n'repo_config_values()'. All current callers indeed pass\n'the_repository', so this new enforcement does not harm them.\n\nSimilar to the recent editor_program migration, no standalone getter\nis introduced to keep the code minimal. The dynamically allocated\nmemory is now managed by 'repo_config_values_clear()'.\n\nOn top of that, fix memory leaks in pager.c while we are at it.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  2 ++\n environment.h |  1 +\n pager.c       | 32 +++++++++++++++++++++++---------\n 3 files changed, 26 insertions(+), 9 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex a65d575af4..975c9cb9eb 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -725,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n+\tcfg->pager_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -742,4 +743,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n+\tFREE_AND_NULL(cfg->pager_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 8178ebab76..39b6691b47 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tchar *excludes_file;\n \tchar *editor_program;\n+\tchar *pager_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/pager.c b/pager.c\nindex 35b210e048..543ef12936 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -5,6 +5,8 @@\n #include \"run-command.h\"\n #include \"sigchain.h\"\n #include \"alias.h\"\n+#include \"repository.h\"\n+#include \"environment.h\"\n \n int pager_use_color = 1;\n \n@@ -13,7 +15,6 @@ int pager_use_color = 1;\n #endif\n \n static struct child_process pager_process;\n-static char *pager_program;\n static int old_fd1 = -1, old_fd2 = -1;\n \n /* Is the value coming back from term_columns() just a guess? */\n@@ -75,10 +76,17 @@ static void wait_for_pager_signal(int signo)\n \n static int core_pager_config(const char *var, const char *value,\n \t\t\t     const struct config_context *ctx UNUSED,\n-\t\t\t     void *data UNUSED)\n+\t\t\t     void *data)\n {\n-\tif (!strcmp(var, \"core.pager\"))\n-\t\treturn git_config_string(&pager_program, var, value);\n+\tstruct repository *r = data;\n+\n+\tif (!strcmp(var, \"core.pager\")) {\n+\t\tstruct repo_config_values *cfg = repo_config_values(r);\n+\n+\t\tFREE_AND_NULL(cfg->pager_program);\n+\t\treturn git_config_string(&cfg->pager_program, var, value);\n+\t}\n+\n \treturn 0;\n }\n \n@@ -91,10 +99,12 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n \n \tpager = getenv(\"GIT_PAGER\");\n \tif (!pager) {\n-\t\tif (!pager_program)\n+\t\tstruct repo_config_values *cfg = repo_config_values(r);\n+\n+\t\tif (!cfg->pager_program)\n \t\t\tread_early_config(r,\n-\t\t\t\t\t  core_pager_config, NULL);\n-\t\tpager = pager_program;\n+\t\t\t\t\t  core_pager_config, r);\n+\t\tpager = cfg->pager_program;\n \t}\n \tif (!pager)\n \t\tpager = getenv(\"PAGER\");\n@@ -302,7 +312,11 @@ int check_pager_config(struct repository *r, const char *cmd)\n \n \tread_early_config(r, pager_command_config, &data);\n \n-\tif (data.value)\n-\t\tpager_program = data.value;\n+\tif (data.value) {\n+\t\tstruct repo_config_values *cfg = repo_config_values(r);\n+\n+\t\tfree(cfg->pager_program);\n+\t\tcfg->pager_program = data.value;\n+\t}\n \treturn data.want;\n }\n-- \n2.43.0\n\n"},{"id":"547935","messageId":"20260713035738.1606138-6-cat@malon.dev","threadId":"65873","inReplyTo":"20260713035738.1606138-1-cat@malon.dev","subject":"[PATCH v11 05/10] environment: move askpass_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-13T03:57:33Z","receivedAt":"2026-07-13T03:58:06Z","isPatch":true,"body":"The global variable 'askpass_program' stores the path to the program\nused to prompt the user for credentials. Move it into repo_config_values\nto continue the libification effort.\n\nWhile it is uncommon for a single process to require different askpass\nprograms for different repositories, maintaining this value as a mutable\nglobal string is a blocker for libification. Global heap-allocated\nstrings introduce thread-safety issues in a multi-repo environment.\n\nMove 'askpass_program' into 'struct repo_config_values' to eliminate\nthis global state. The memory is now safely managed and freed via\n'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 7 ++++---\n environment.h | 3 +--\n prompt.c      | 3 ++-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 975c9cb9eb..3857818da3 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -464,8 +463,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.askpass\")) {\n-\t\tFREE_AND_NULL(askpass_program);\n-\t\treturn git_config_string(&askpass_program, var, value);\n+\t\tFREE_AND_NULL(cfg->askpass_program);\n+\t\treturn git_config_string(&cfg->askpass_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n@@ -726,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n+\tcfg->askpass_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -744,4 +744,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n+\tFREE_AND_NULL(cfg->askpass_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 39b6691b47..856dc70cc4 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -93,6 +93,7 @@ struct repo_config_values {\n \tchar *excludes_file;\n \tchar *editor_program;\n \tchar *pager_program;\n+\tchar *askpass_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -220,8 +221,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *askpass_program;\n-\n /*\n  * The character that begins a commented line in user-editable file\n  * that is subject to stripspace.\ndiff --git a/prompt.c b/prompt.c\nindex 706fba2a50..d8d74c7e37 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -3,6 +3,7 @@\n #include \"git-compat-util.h\"\n #include \"parse.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"prompt.h\"\n@@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)\n \n \t\taskpass = getenv(\"GIT_ASKPASS\");\n \t\tif (!askpass)\n-\t\t\taskpass = askpass_program;\n+\t\t\taskpass = repo_config_values(the_repository)->askpass_program;\n \t\tif (!askpass)\n \t\t\taskpass = getenv(\"SSH_ASKPASS\");\n \t\tif (askpass && *askpass)\n-- \n2.43.0\n\n"},{"id":"547936","messageId":"20260713035738.1606138-7-cat@malon.dev","threadId":"65873","inReplyTo":"20260713035738.1606138-1-cat@malon.dev","subject":"[PATCH v11 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-13T03:57:34Z","receivedAt":"2026-07-13T03:58:10Z","isPatch":true,"body":"The global variables 'apply_default_whitespace' and\n'apply_default_ignorewhitespace' are used to store the default\nwhitespace configuration for 'git apply'. Move these variables\ninto 'struct repo_config_values' to continue the libification\neffort.\n\nDynamically allocated strings fetched via 'repo_config_get_string()'\nare now tracked per-repository and safely freed in\n'repo_config_values_clear()'.\n\nAs part of this transition, update 'git_apply_config()' to accept a\n'struct repository *' argument rather than relying on the\n'the_repository' global.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n apply.c       | 28 ++++++++++++++++++++--------\n environment.c |  6 ++++--\n environment.h |  4 ++--\n 3 files changed, 26 insertions(+), 12 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..f0cfd76190 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -47,11 +47,17 @@ struct gitdiff_data {\n \tint p_value;\n };\n \n-static void git_apply_config(void)\n+static void git_apply_config(struct repository *repo)\n {\n-\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n-\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n-\trepo_config(the_repository, git_xmerge_config, NULL);\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\trepo_config_get_string(repo, \"apply.whitespace\",\n+\t\t\t       &cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n+\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n+\t\t\t       &cfg->apply_default_ignorewhitespace);\n+\trepo_config(repo, git_xmerge_config, NULL);\n }\n \n static int parse_whitespace_option(struct apply_state *state, const char *option)\n@@ -126,10 +132,15 @@ int init_apply_state(struct apply_state *state,\n \tstrset_init(&state->kept_symlinks);\n \tstrbuf_init(&state->root, 0);\n \n-\tgit_apply_config();\n-\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n+\tgit_apply_config(repo);\n+\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n+\n+\tif (cfg->apply_default_whitespace &&\n+\t    parse_whitespace_option(state, cfg->apply_default_whitespace))\n \t\treturn -1;\n-\tif (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))\n+\tif (cfg->apply_default_ignorewhitespace &&\n+\t    parse_ignorewhitespace_option(state, cfg->apply_default_ignorewhitespace))\n \t\treturn -1;\n \treturn 0;\n }\n@@ -192,7 +203,8 @@ int check_apply_state(struct apply_state *state, int force_apply)\n \n static void set_default_whitespace_mode(struct apply_state *state)\n {\n-\tif (!state->whitespace_option && !apply_default_whitespace)\n+\tif (!state->whitespace_option &&\n+\t    !repo_config_values(state->repo)->apply_default_whitespace)\n \t\tstate->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);\n }\n \ndiff --git a/environment.c b/environment.c\nindex 3857818da3..20500658a2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,8 +49,6 @@ int assume_unchanged;\n int is_bare_repository_cfg = -1; /* unspecified */\n char *git_commit_encoding;\n char *git_log_output_encoding;\n-char *apply_default_whitespace;\n-char *apply_default_ignorewhitespace;\n int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n@@ -726,6 +724,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n \tcfg->askpass_program = NULL;\n+\tcfg->apply_default_whitespace = NULL;\n+\tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -745,4 +745,6 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n \tFREE_AND_NULL(cfg->askpass_program);\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n }\ndiff --git a/environment.h b/environment.h\nindex 856dc70cc4..f450242ac0 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -94,6 +94,8 @@ struct repo_config_values {\n \tchar *editor_program;\n \tchar *pager_program;\n \tchar *askpass_program;\n+\tchar *apply_default_whitespace;\n+\tchar *apply_default_ignorewhitespace;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -182,8 +184,6 @@ extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\n-extern char *apply_default_whitespace;\n-extern char *apply_default_ignorewhitespace;\n extern unsigned long pack_size_limit_cfg;\n \n extern int protect_hfs;\n-- \n2.43.0\n\n"},{"id":"548023","messageId":"xmqqa4ruyhbh.fsf@gitster.g","threadId":"65873","inReplyTo":"20260713035738.1606138-7-cat@malon.dev","subject":"Re: [PATCH v11 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-13T16:39:46Z","receivedAt":"2026-07-13T16:39:49Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> Subject: Re: [PATCH v11 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace\n\nAre there patches 7..10/10 posted somewhere else?  I didn't see them\nin the thread (neither did \"b4\").\n\n>  \n> -static void git_apply_config(void)\n> +static void git_apply_config(struct repository *repo)\n>  {\n> -\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n> -\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n> -\trepo_config(the_repository, git_xmerge_config, NULL);\n> +\tstruct repo_config_values *cfg = repo_config_values(repo);\n> +\n> +\tFREE_AND_NULL(cfg->apply_default_whitespace);\n> +\trepo_config_get_string(repo, \"apply.whitespace\",\n> +\t\t\t       &cfg->apply_default_whitespace);\n> +\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n> +\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n> +\t\t\t       &cfg->apply_default_ignorewhitespace);\n> +\trepo_config(repo, git_xmerge_config, NULL);\n>  }\n\nOK.\n\n>  static int parse_whitespace_option(struct apply_state *state, const char *option)\n> @@ -126,10 +132,15 @@ int init_apply_state(struct apply_state *state,\n>  \tstrset_init(&state->kept_symlinks);\n>  \tstrbuf_init(&state->root, 0);\n>  \n> -\tgit_apply_config();\n> -\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n> +\tgit_apply_config(repo);\n> +\n> +\tstruct repo_config_values *cfg = repo_config_values(repo);\n\nDoesn't \"-Wdeclaration-after-statement\" complain on this, declaring cfg\nafter calling \"git_apply_config(repo)\" on the line before?\n\n"},{"id":"548055","messageId":"61ef1b0a-72e8-45b3-b6e8-46eb5b34ea91@malon.dev","threadId":"65873","inReplyTo":"xmqqa4ruyhbh.fsf@gitster.g","subject":"Re: [PATCH v11 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:19:37Z","receivedAt":"2026-07-14T03:19:45Z","isPatch":true,"body":"On 7/14/26 00:39, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> Subject: Re: [PATCH v11 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace\n> \n> Are there patches 7..10/10 posted somewhere else?  I didn't see them\n> in the thread (neither did \"b4\").\n> \n\nOh, I didn't notice that:\n\n\tDied at /usr/lib/git-core/git-send-email line 1665.\n\nWill resend very soon.\n\n>>   \n>> -static void git_apply_config(void)\n>> +static void git_apply_config(struct repository *repo)\n>>   {\n>> -\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n>> -\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n>> -\trepo_config(the_repository, git_xmerge_config, NULL);\n>> +\tstruct repo_config_values *cfg = repo_config_values(repo);\n>> +\n>> +\tFREE_AND_NULL(cfg->apply_default_whitespace);\n>> +\trepo_config_get_string(repo, \"apply.whitespace\",\n>> +\t\t\t       &cfg->apply_default_whitespace);\n>> +\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n>> +\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n>> +\t\t\t       &cfg->apply_default_ignorewhitespace);\n>> +\trepo_config(repo, git_xmerge_config, NULL);\n>>   }\n> \n> OK.\n> \n>>   static int parse_whitespace_option(struct apply_state *state, const char *option)\n>> @@ -126,10 +132,15 @@ int init_apply_state(struct apply_state *state,\n>>   \tstrset_init(&state->kept_symlinks);\n>>   \tstrbuf_init(&state->root, 0);\n>>   \n>> -\tgit_apply_config();\n>> -\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n>> +\tgit_apply_config(repo);\n>> +\n>> +\tstruct repo_config_values *cfg = repo_config_values(repo);\n> \n> Doesn't \"-Wdeclaration-after-statement\" complain on this, declaring cfg\n> after calling \"git_apply_config(repo)\" on the line before?\n> \n\nNice catch, thanks!\n\nRegards, yuchen\n\n"},{"id":"548056","messageId":"20260714032525.1611141-1-cat@malon.dev","threadId":"65873","inReplyTo":"20260712111734.1073514-1-cat@malon.dev","subject":"[PATCH v12 00/10] migrate more variables into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:15Z","receivedAt":"2026-07-14T03:25:32Z","isPatch":true,"body":"Hi everyone,\n\nThis patch series continues the ongoing libification effort by migrating\na batch of global configuration variables into struct repo_config_values.\n\nWhat does this series do:\n\ninfrastructure & strings (commits 1-6):\nIntroduce 'repo_config_values_clear()' to manage the lifecycle\nof heap-allocated configuration strings. This infrastructure is utilized\nto migrate string variables, including 'excludes_file', 'apply' whitespace\nconfigs, and external programs including 'editor', 'pager', 'askpass'.\n\nenums (commits 7-9):\nMigrate enumerations 'push_default', 'autorebase', and\n'object_creation_mode'. Care was taken to make these types available\nto the configuration structure without triggering circular header\ndependencies.\n\nedit comment (commit 10):\nAdjust the comment for config_values_private_ in repository.h.\n\nRFC:\n\nCommit 3~5. Is it really necessary to migrate _program variables?\nhttps://lore.kernel.org/git/8e657184-ee0b-453a-9f2d-a98080d3582e@gmail.com/\n\nCommit 6~9. Previous related discussions on 'git_branch_track'.\nhttps://lore.kernel.org/git/CAD=f0L-mPX+KECUjXk-WBzEbTP7wCa8sB56GySQT0yh9mfUOWw@mail.gmail.com/\n\nNote:\n\nSince a new getter 'repo_excludes_file()' is introduced, as previously\npromised, once it is finally merged into 'master', there will be a patch to\nupdate and squash the comments.\n\nSimilarly, I've noticed that the classification and sorting of variables in\n'repo_config_values' don't seem to be correct. There will also be a patch\nto fix this, and I think it will form a commit series along with the comment\npatch?\n\nChanges since v11:\n\n - Resending commit 7~10/10, which were not sent in V11 due to network\n issue.\n\n - In commit 6/10, fix a declaration-after-statement error in apply.c\n\nSpecial thanks to Pablo and Junio!\n\nTian Yuchen (10):\n  repository: introduce repo_config_values_clear()\n  environment: move excludes_file into repo_config_values\n  environment: move editor_program into repo_config_values\n  environment: move pager_program into repo_config_values\n  environment: move askpass_program into repo_config_values\n  environment: migrate apply_default_whitespace and\n    apply_default_ignorewhitespace\n  environment: move push_default into repo_config_values\n  environment: move autorebase into repo_config_values\n  environment: move object_creation_mode into repo_config_values\n  repository: adjust the comment of config_values_private_\n\n apply.c        | 28 ++++++++++++------\n branch.c       |  2 +-\n builtin/push.c | 10 ++++---\n dir.c          |  4 +--\n editor.c       |  4 +--\n environment.c  | 76 ++++++++++++++++++++++++++++++++-----------------\n environment.h  | 77 ++++++++++++++++++++++++++++++--------------------\n object-file.c  |  3 +-\n pager.c        | 32 +++++++++++++++------\n prompt.c       |  3 +-\n remote.c       |  2 +-\n repository.c   |  1 +\n repository.h   |  2 +-\n 13 files changed, 158 insertions(+), 86 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"548057","messageId":"20260714032525.1611141-2-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 01/10] repository: introduce repo_config_values_clear()","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:16Z","receivedAt":"2026-07-14T03:25:35Z","isPatch":true,"body":"As part of the ongoing libification effort, dynamically allocated\nglobal configuration variables are being moved into\n'struct repo_config_values'. To prevent memory leaks, we need a\ndestructor to free these heap-allocated variables when a repository\ninstance is torn down.\n\nIntroduce 'repo_config_values_clear()' in environment.c and invoke it\nfrom 'repo_clear()' in repository.c. As a starting point, update this\nnew function to handle the cleanup of 'attributes_file'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 5 +++++\n environment.h | 9 +++++++++\n repository.c  | 1 +\n 3 files changed, 15 insertions(+)\n\ndiff --git a/environment.c b/environment.c\nindex ba2c60103f..ae05f16d04 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -726,3 +726,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->sparse_expect_files_outside_of_patterns = 0;\n \tcfg->warn_on_object_refname_ambiguity = 1;\n }\n+\n+void repo_config_values_clear(struct repo_config_values *cfg)\n+{\n+\tFREE_AND_NULL(cfg->attributes_file);\n+}\ndiff --git a/environment.h b/environment.h\nindex 6f18286955..9169d7f62d 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value,\n \n void repo_config_values_init(struct repo_config_values *cfg);\n \n+/*\n+ * Frees memory allocated for dynamically loaded configuration values\n+ * inside `repo_config_values`.\n+ *\n+ * As dynamically allocated variables are migrated into this struct,\n+ * their FREE_AND_NULL() calls should be appended here.\n+ */\n+void repo_config_values_clear(struct repo_config_values *cfg);\n+\n /*\n  * TODO: All the below state either explicitly or implicitly relies on\n  * `the_repository`. We should eventually get rid of these and make the\ndiff --git a/repository.c b/repository.c\nindex 187dd471c4..669e2d1200 100644\n--- a/repository.c\n+++ b/repository.c\n@@ -388,6 +388,7 @@ void repo_clear(struct repository *repo)\n \tFREE_AND_NULL(repo->parsed_objects);\n \n \trepo_settings_clear(repo);\n+\trepo_config_values_clear(&repo->config_values_private_);\n \n \tif (repo->config) {\n \t\tgit_configset_clear(repo->config);\n-- \n2.43.0\n\n"},{"id":"548058","messageId":"20260714032525.1611141-3-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 02/10] environment: move excludes_file into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:17Z","receivedAt":"2026-07-14T03:25:40Z","isPatch":true,"body":"The global variable 'excludes_file' is used to track the path to the\nglobal ignore file. If this variable is NULL,\n'setup_standard_excludes()' in 'dir.c' forcefully evaluates and assigns\nthe XDG default path to it.\n\nContinue the libification effort by encapsulating this lazy-loading\nfallback logic into a proper getter and moving the variable into\n'struct repo_config_values'.\n\nSince 'excludes_file' is a dynamically allocated string, it requires\nproper heap memory management. It is safely freed using the newly\nintroduced 'repo_config_values_clear()' function when the repository\nis torn down.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n dir.c         |  4 ++--\n environment.c | 17 ++++++++++++++---\n environment.h |  4 +++-\n 3 files changed, 19 insertions(+), 6 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 7a73690fbc..4f87a52b3c 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, \"info/exclude\")\n \n void setup_standard_excludes(struct dir_struct *dir)\n {\n+\tconst char *excludes_file = repo_excludes_file(the_repository);\n+\n \tdir->exclude_per_dir = \".gitignore\";\n \n \t/* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */\n-\tif (!excludes_file)\n-\t\texcludes_file = xdg_config_home(\"ignore\");\n \tif (excludes_file && !access_or_warn(excludes_file, R_OK, 0))\n \t\tadd_patterns_from_file_1(dir, excludes_file,\n \t\t\t\t\t dir->untracked ? &dir->internal.ss_excludes_file : NULL);\ndiff --git a/environment.c b/environment.c\nindex ae05f16d04..275931c213 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n char *editor_program;\n char *askpass_program;\n-char *excludes_file;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -134,6 +133,16 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+const char *repo_excludes_file(struct repository *repo)\n+{\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n+\n+\tif (!cfg->excludes_file)\n+\t\tcfg->excludes_file = xdg_config_home(\"ignore\");\n+\n+\treturn cfg->excludes_file;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -461,8 +470,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n-\t\tFREE_AND_NULL(excludes_file);\n-\t\treturn git_config_pathname(&excludes_file, var, value);\n+\t\tFREE_AND_NULL(cfg->excludes_file);\n+\t\treturn git_config_pathname(&cfg->excludes_file, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.whitespace\")) {\n@@ -715,6 +724,7 @@ int git_default_config(const char *var, const char *value,\n void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n+\tcfg->excludes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -730,4 +740,5 @@ void repo_config_values_init(struct repo_config_values *cfg)\n void repo_config_values_clear(struct repo_config_values *cfg)\n {\n \tFREE_AND_NULL(cfg->attributes_file);\n+\tFREE_AND_NULL(cfg->excludes_file);\n }\ndiff --git a/environment.h b/environment.h\nindex 9169d7f62d..4776ccc657 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -90,6 +90,7 @@ struct repository;\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n+\tchar *excludes_file;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -133,6 +134,8 @@ int git_default_config(const char *, const char *,\n int git_default_core_config(const char *var, const char *value,\n \t\t\t    const struct config_context *ctx, void *cb);\n \n+const char *repo_excludes_file(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -217,7 +220,6 @@ extern char *git_log_output_encoding;\n \n extern char *editor_program;\n extern char *askpass_program;\n-extern char *excludes_file;\n \n /*\n  * The character that begins a commented line in user-editable file\n-- \n2.43.0\n\n"},{"id":"548059","messageId":"20260714032525.1611141-4-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 03/10] environment: move editor_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:18Z","receivedAt":"2026-07-14T03:25:43Z","isPatch":true,"body":"The global variable 'editor_program' holds the path to the user's\npreferred editor. Move 'editor_program' into\n'struct repo_config_values' to continue the libification effort.\n\nThere have been discussions on whether external programs like\neditors truly need to be configured on a per-repository basis within\nthe same process. While a single process might rarely invoke\ndifferent editors, this migration is necessary for two reasons:\n\n1. Developers frequently use different toolchains for different\n   projects. Per-repo configuration respects this.\n\n2. Moving this string into 'repo_config_values' eliminates mutable\n   global state. As the codebase moves toward becoming a long-running\n   processes, managing multiple repositories concurrently must\n   not overwrite each other's program configurations.\n\nNo standalone getter function is introduced. Callers directly access\nthe field via 'repo_config_values()'. Heap memory is safely reclaimed\nin 'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n editor.c      | 4 ++--\n environment.c | 7 ++++---\n environment.h | 2 +-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/editor.c b/editor.c\nindex fd174e6a03..0d1cb8768d 100644\n--- a/editor.c\n+++ b/editor.c\n@@ -29,8 +29,8 @@ const char *git_editor(void)\n \tconst char *editor = getenv(\"GIT_EDITOR\");\n \tint terminal_is_dumb = is_terminal_dumb();\n \n-\tif (!editor && editor_program)\n-\t\teditor = editor_program;\n+\tif (!editor)\n+\t\teditor = repo_config_values(the_repository)->editor_program;\n \tif (!editor && !terminal_is_dumb)\n \t\teditor = getenv(\"VISUAL\");\n \tif (!editor)\ndiff --git a/environment.c b/environment.c\nindex 275931c213..a65d575af4 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *editor_program;\n char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n@@ -437,8 +436,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.editor\")) {\n-\t\tFREE_AND_NULL(editor_program);\n-\t\treturn git_config_string(&editor_program, var, value);\n+\t\tFREE_AND_NULL(cfg->editor_program);\n+\t\treturn git_config_string(&cfg->editor_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.commentchar\") ||\n@@ -725,6 +724,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n+\tcfg->editor_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -741,4 +741,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n {\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n+\tFREE_AND_NULL(cfg->editor_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 4776ccc657..8178ebab76 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -91,6 +91,7 @@ struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n \tchar *excludes_file;\n+\tchar *editor_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *editor_program;\n extern char *askpass_program;\n \n /*\n-- \n2.43.0\n\n"},{"id":"548060","messageId":"20260714032525.1611141-5-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 04/10] environment: move pager_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:19Z","receivedAt":"2026-07-14T03:25:47Z","isPatch":true,"body":"The 'pager_program' variable is currently defined as a file-scoped\nstatic string in pager.c. Move it into 'struct repo_config_values'.\n\nThe configuration parsing logic remains strictly within pager.c to\nrespect subsystem boundaries. The read/write operations are simply\nredirected to the repository-specific structure using\n'repo_config_values()'. All current callers indeed pass\n'the_repository', so this new enforcement does not harm them.\n\nSimilar to the recent editor_program migration, no standalone getter\nis introduced to keep the code minimal. The dynamically allocated\nmemory is now managed by 'repo_config_values_clear()'.\n\nOn top of that, fix memory leaks in pager.c while we are at it.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  2 ++\n environment.h |  1 +\n pager.c       | 32 +++++++++++++++++++++++---------\n 3 files changed, 26 insertions(+), 9 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex a65d575af4..975c9cb9eb 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -725,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n+\tcfg->pager_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -742,4 +743,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->attributes_file);\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n+\tFREE_AND_NULL(cfg->pager_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 8178ebab76..39b6691b47 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tchar *excludes_file;\n \tchar *editor_program;\n+\tchar *pager_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\ndiff --git a/pager.c b/pager.c\nindex 35b210e048..543ef12936 100644\n--- a/pager.c\n+++ b/pager.c\n@@ -5,6 +5,8 @@\n #include \"run-command.h\"\n #include \"sigchain.h\"\n #include \"alias.h\"\n+#include \"repository.h\"\n+#include \"environment.h\"\n \n int pager_use_color = 1;\n \n@@ -13,7 +15,6 @@ int pager_use_color = 1;\n #endif\n \n static struct child_process pager_process;\n-static char *pager_program;\n static int old_fd1 = -1, old_fd2 = -1;\n \n /* Is the value coming back from term_columns() just a guess? */\n@@ -75,10 +76,17 @@ static void wait_for_pager_signal(int signo)\n \n static int core_pager_config(const char *var, const char *value,\n \t\t\t     const struct config_context *ctx UNUSED,\n-\t\t\t     void *data UNUSED)\n+\t\t\t     void *data)\n {\n-\tif (!strcmp(var, \"core.pager\"))\n-\t\treturn git_config_string(&pager_program, var, value);\n+\tstruct repository *r = data;\n+\n+\tif (!strcmp(var, \"core.pager\")) {\n+\t\tstruct repo_config_values *cfg = repo_config_values(r);\n+\n+\t\tFREE_AND_NULL(cfg->pager_program);\n+\t\treturn git_config_string(&cfg->pager_program, var, value);\n+\t}\n+\n \treturn 0;\n }\n \n@@ -91,10 +99,12 @@ const char *git_pager(struct repository *r, int stdout_is_tty)\n \n \tpager = getenv(\"GIT_PAGER\");\n \tif (!pager) {\n-\t\tif (!pager_program)\n+\t\tstruct repo_config_values *cfg = repo_config_values(r);\n+\n+\t\tif (!cfg->pager_program)\n \t\t\tread_early_config(r,\n-\t\t\t\t\t  core_pager_config, NULL);\n-\t\tpager = pager_program;\n+\t\t\t\t\t  core_pager_config, r);\n+\t\tpager = cfg->pager_program;\n \t}\n \tif (!pager)\n \t\tpager = getenv(\"PAGER\");\n@@ -302,7 +312,11 @@ int check_pager_config(struct repository *r, const char *cmd)\n \n \tread_early_config(r, pager_command_config, &data);\n \n-\tif (data.value)\n-\t\tpager_program = data.value;\n+\tif (data.value) {\n+\t\tstruct repo_config_values *cfg = repo_config_values(r);\n+\n+\t\tfree(cfg->pager_program);\n+\t\tcfg->pager_program = data.value;\n+\t}\n \treturn data.want;\n }\n-- \n2.43.0\n\n"},{"id":"548061","messageId":"20260714032525.1611141-6-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 05/10] environment: move askpass_program into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:20Z","receivedAt":"2026-07-14T03:25:50Z","isPatch":true,"body":"The global variable 'askpass_program' stores the path to the program\nused to prompt the user for credentials. Move it into repo_config_values\nto continue the libification effort.\n\nWhile it is uncommon for a single process to require different askpass\nprograms for different repositories, maintaining this value as a mutable\nglobal string is a blocker for libification. Global heap-allocated\nstrings introduce thread-safety issues in a multi-repo environment.\n\nMove 'askpass_program' into 'struct repo_config_values' to eliminate\nthis global state. The memory is now safely managed and freed via\n'repo_config_values_clear()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c | 7 ++++---\n environment.h | 3 +--\n prompt.c      | 3 ++-\n 3 files changed, 7 insertions(+), 6 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex 975c9cb9eb..3857818da3 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -55,7 +55,6 @@ int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT;\n-char *askpass_program;\n enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n@@ -464,8 +463,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.askpass\")) {\n-\t\tFREE_AND_NULL(askpass_program);\n-\t\treturn git_config_string(&askpass_program, var, value);\n+\t\tFREE_AND_NULL(cfg->askpass_program);\n+\t\treturn git_config_string(&cfg->askpass_program, var, value);\n \t}\n \n \tif (!strcmp(var, \"core.excludesfile\")) {\n@@ -726,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->excludes_file = NULL;\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n+\tcfg->askpass_program = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -744,4 +744,5 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->excludes_file);\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n+\tFREE_AND_NULL(cfg->askpass_program);\n }\ndiff --git a/environment.h b/environment.h\nindex 39b6691b47..856dc70cc4 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -93,6 +93,7 @@ struct repo_config_values {\n \tchar *excludes_file;\n \tchar *editor_program;\n \tchar *pager_program;\n+\tchar *askpass_program;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -220,8 +221,6 @@ const char *get_commit_output_encoding(void);\n extern char *git_commit_encoding;\n extern char *git_log_output_encoding;\n \n-extern char *askpass_program;\n-\n /*\n  * The character that begins a commented line in user-editable file\n  * that is subject to stripspace.\ndiff --git a/prompt.c b/prompt.c\nindex 706fba2a50..d8d74c7e37 100644\n--- a/prompt.c\n+++ b/prompt.c\n@@ -3,6 +3,7 @@\n #include \"git-compat-util.h\"\n #include \"parse.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n #include \"prompt.h\"\n@@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags)\n \n \t\taskpass = getenv(\"GIT_ASKPASS\");\n \t\tif (!askpass)\n-\t\t\taskpass = askpass_program;\n+\t\t\taskpass = repo_config_values(the_repository)->askpass_program;\n \t\tif (!askpass)\n \t\t\taskpass = getenv(\"SSH_ASKPASS\");\n \t\tif (askpass && *askpass)\n-- \n2.43.0\n\n"},{"id":"548062","messageId":"20260714032525.1611141-7-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:21Z","receivedAt":"2026-07-14T03:25:55Z","isPatch":true,"body":"The global variables 'apply_default_whitespace' and\n'apply_default_ignorewhitespace' are used to store the default\nwhitespace configuration for 'git apply'. Move these variables\ninto 'struct repo_config_values' to continue the libification\neffort.\n\nDynamically allocated strings fetched via 'repo_config_get_string()'\nare now tracked per-repository and safely freed in\n'repo_config_values_clear()'.\n\nAs part of this transition, update 'git_apply_config()' to accept a\n'struct repository *' argument rather than relying on the\n'the_repository' global.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n apply.c       | 28 ++++++++++++++++++++--------\n environment.c |  6 ++++--\n environment.h |  4 ++--\n 3 files changed, 26 insertions(+), 12 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..b1db1fe495 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -47,11 +47,17 @@ struct gitdiff_data {\n \tint p_value;\n };\n \n-static void git_apply_config(void)\n+static void git_apply_config(struct repository *repo)\n {\n-\trepo_config_get_string(the_repository, \"apply.whitespace\", &apply_default_whitespace);\n-\trepo_config_get_string(the_repository, \"apply.ignorewhitespace\", &apply_default_ignorewhitespace);\n-\trepo_config(the_repository, git_xmerge_config, NULL);\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n+\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\trepo_config_get_string(repo, \"apply.whitespace\",\n+\t\t\t       &cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n+\trepo_config_get_string(repo, \"apply.ignorewhitespace\",\n+\t\t\t       &cfg->apply_default_ignorewhitespace);\n+\trepo_config(repo, git_xmerge_config, NULL);\n }\n \n static int parse_whitespace_option(struct apply_state *state, const char *option)\n@@ -109,6 +115,8 @@ int init_apply_state(struct apply_state *state,\n \t\t     struct repository *repo,\n \t\t     const char *prefix)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n+\n \tmemset(state, 0, sizeof(*state));\n \tstate->prefix = prefix;\n \tstate->repo = repo;\n@@ -126,10 +134,13 @@ int init_apply_state(struct apply_state *state,\n \tstrset_init(&state->kept_symlinks);\n \tstrbuf_init(&state->root, 0);\n \n-\tgit_apply_config();\n-\tif (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace))\n+\tgit_apply_config(repo);\n+\n+\tif (cfg->apply_default_whitespace &&\n+\t    parse_whitespace_option(state, cfg->apply_default_whitespace))\n \t\treturn -1;\n-\tif (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace))\n+\tif (cfg->apply_default_ignorewhitespace &&\n+\t    parse_ignorewhitespace_option(state, cfg->apply_default_ignorewhitespace))\n \t\treturn -1;\n \treturn 0;\n }\n@@ -192,7 +203,8 @@ int check_apply_state(struct apply_state *state, int force_apply)\n \n static void set_default_whitespace_mode(struct apply_state *state)\n {\n-\tif (!state->whitespace_option && !apply_default_whitespace)\n+\tif (!state->whitespace_option &&\n+\t    !repo_config_values(state->repo)->apply_default_whitespace)\n \t\tstate->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error);\n }\n \ndiff --git a/environment.c b/environment.c\nindex 3857818da3..20500658a2 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -49,8 +49,6 @@ int assume_unchanged;\n int is_bare_repository_cfg = -1; /* unspecified */\n char *git_commit_encoding;\n char *git_log_output_encoding;\n-char *apply_default_whitespace;\n-char *apply_default_ignorewhitespace;\n int fsync_object_files = -1;\n int use_fsync = -1;\n enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT;\n@@ -726,6 +724,8 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->editor_program = NULL;\n \tcfg->pager_program = NULL;\n \tcfg->askpass_program = NULL;\n+\tcfg->apply_default_whitespace = NULL;\n+\tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\n@@ -745,4 +745,6 @@ void repo_config_values_clear(struct repo_config_values *cfg)\n \tFREE_AND_NULL(cfg->editor_program);\n \tFREE_AND_NULL(cfg->pager_program);\n \tFREE_AND_NULL(cfg->askpass_program);\n+\tFREE_AND_NULL(cfg->apply_default_whitespace);\n+\tFREE_AND_NULL(cfg->apply_default_ignorewhitespace);\n }\ndiff --git a/environment.h b/environment.h\nindex 856dc70cc4..f450242ac0 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -94,6 +94,8 @@ struct repo_config_values {\n \tchar *editor_program;\n \tchar *pager_program;\n \tchar *askpass_program;\n+\tchar *apply_default_whitespace;\n+\tchar *apply_default_ignorewhitespace;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -182,8 +184,6 @@ extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\n-extern char *apply_default_whitespace;\n-extern char *apply_default_ignorewhitespace;\n extern unsigned long pack_size_limit_cfg;\n \n extern int protect_hfs;\n-- \n2.43.0\n\n"},{"id":"548063","messageId":"20260714032525.1611141-8-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 07/10] environment: move push_default into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:22Z","receivedAt":"2026-07-14T03:25:57Z","isPatch":true,"body":"The global variable 'push_default' specifies the default behavior of\n'git push' when no explicit refspec is provided. Move 'push_default'\ninto 'struct repo_config_values' to continue the libification effort.\n\nWhile 'enum push_default_type' ideally belongs in 'remote.h', moving it\nthere introduces a circular dependency chain:\n\n  remote.h -> hash.h -> repository.h -> environment.h.\n\nTherefore, the enum definition is kept in 'environment.h' just above\n'struct repo_config_values' with a NEEDSWORK comment for future cleanup.\n\nModify the configuration parsing in environment.c to update the\nper-repository structure directly, and update caller across the\ncodebase to access the value via 'repo_config_values()'.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n builtin/push.c | 10 ++++++----\n environment.c  | 16 +++++++++-------\n environment.h  | 26 ++++++++++++++++----------\n remote.c       |  2 +-\n 4 files changed, 32 insertions(+), 22 deletions(-)\n\ndiff --git a/builtin/push.c b/builtin/push.c\nindex 6021b71d66..7578ff38c4 100644\n--- a/builtin/push.c\n+++ b/builtin/push.c\n@@ -73,6 +73,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,\n \t\t\t\t  struct remote *remote, struct ref *matched)\n {\n \tconst char *branch_name;\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n \n \tif (remote->push.nr) {\n \t\tstruct refspec_item query = {\n@@ -88,7 +89,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref,\n \t\t}\n \t}\n \n-\tif (push_default == PUSH_DEFAULT_UPSTREAM &&\n+\tif (cfg->push_default == PUSH_DEFAULT_UPSTREAM &&\n \t    skip_prefix(matched->name, \"refs/heads/\", &branch_name)) {\n \t\tstruct branch *branch = branch_get(branch_name);\n \t\tif (branch->merge_nr == 1 && branch->merge[0]->src) {\n@@ -160,7 +161,7 @@ static NORETURN void die_push_simple(struct branch *branch,\n \t * Don't show advice for people who explicitly set\n \t * push.default.\n \t */\n-\tif (push_default == PUSH_DEFAULT_UNSPECIFIED)\n+\tif (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED)\n \t\tadvice_pushdefault_maybe = _(\"\\n\"\n \t\t\t\t \"To choose either option permanently, \"\n \t\t\t\t \"see push.default in 'git help config'.\\n\");\n@@ -231,8 +232,9 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tstruct branch *branch;\n \tconst char *dst;\n \tint same_remote;\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n \n-\tswitch (push_default) {\n+\tswitch (cfg->push_default) {\n \tcase PUSH_DEFAULT_MATCHING:\n \t\trefspec_append(&rs, \":\");\n \t\treturn;\n@@ -252,7 +254,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote)\n \tdst = branch->refname;\n \tsame_remote = !strcmp(remote->name, remote_for_branch(branch, NULL));\n \n-\tswitch (push_default) {\n+\tswitch (cfg->push_default) {\n \tdefault:\n \tcase PUSH_DEFAULT_UNSPECIFIED:\n \tcase PUSH_DEFAULT_SIMPLE:\ndiff --git a/environment.c b/environment.c\nindex 20500658a2..66c1ac1ab8 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -58,7 +58,6 @@ enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n-enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -620,21 +619,23 @@ static int git_default_branch_config(const char *var, const char *value)\n \n static int git_default_push_config(const char *var, const char *value)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tif (!strcmp(var, \"push.default\")) {\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"nothing\"))\n-\t\t\tpush_default = PUSH_DEFAULT_NOTHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_NOTHING;\n \t\telse if (!strcmp(value, \"matching\"))\n-\t\t\tpush_default = PUSH_DEFAULT_MATCHING;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_MATCHING;\n \t\telse if (!strcmp(value, \"simple\"))\n-\t\t\tpush_default = PUSH_DEFAULT_SIMPLE;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_SIMPLE;\n \t\telse if (!strcmp(value, \"upstream\"))\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"tracking\")) /* deprecated */\n-\t\t\tpush_default = PUSH_DEFAULT_UPSTREAM;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_UPSTREAM;\n \t\telse if (!strcmp(value, \"current\"))\n-\t\t\tpush_default = PUSH_DEFAULT_CURRENT;\n+\t\t\tcfg->push_default = PUSH_DEFAULT_CURRENT;\n \t\telse {\n \t\t\terror(_(\"malformed value for %s: %s\"), var, value);\n \t\t\treturn error(_(\"must be one of nothing, matching, simple, \"\n@@ -726,6 +727,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->askpass_program = NULL;\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n+\tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex f450242ac0..17a3a628d2 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -87,6 +87,21 @@ extern const char * const local_repo_env[];\n struct strvec;\n \n struct repository;\n+\n+/*\n+ * NEEDSWORK: It would be better if these definitions could be moved to\n+ * other more specific files, but care is needed to avoid circular\n+ * inclusion issues.\n+ */\n+enum push_default_type {\n+\tPUSH_DEFAULT_NOTHING = 0,\n+\tPUSH_DEFAULT_MATCHING,\n+\tPUSH_DEFAULT_SIMPLE,\n+\tPUSH_DEFAULT_UPSTREAM,\n+\tPUSH_DEFAULT_CURRENT,\n+\tPUSH_DEFAULT_UNSPECIFIED\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -96,6 +111,7 @@ struct repo_config_values {\n \tchar *askpass_program;\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n+\tenum push_default_type push_default;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -197,16 +213,6 @@ enum rebase_setup_type {\n };\n extern enum rebase_setup_type autorebase;\n \n-enum push_default_type {\n-\tPUSH_DEFAULT_NOTHING = 0,\n-\tPUSH_DEFAULT_MATCHING,\n-\tPUSH_DEFAULT_SIMPLE,\n-\tPUSH_DEFAULT_UPSTREAM,\n-\tPUSH_DEFAULT_CURRENT,\n-\tPUSH_DEFAULT_UNSPECIFIED\n-};\n-extern enum push_default_type push_default;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\ndiff --git a/remote.c b/remote.c\nindex 00723b385e..d48c01d375 100644\n--- a/remote.c\n+++ b/remote.c\n@@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo,\n \tif (remote->mirror)\n \t\treturn tracking_for_push_dest(remote, branch->refname, err);\n \n-\tswitch (push_default) {\n+\tswitch (repo_config_values(repo)->push_default) {\n \tcase PUSH_DEFAULT_NOTHING:\n \t\treturn error_buf(err, _(\"push has no destination (push.default is 'nothing')\"));\n \n-- \n2.43.0\n\n"},{"id":"548064","messageId":"20260714032525.1611141-9-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 08/10] environment: move autorebase into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:23Z","receivedAt":"2026-07-14T03:26:00Z","isPatch":true,"body":"The global variable 'autorebase' dictates whether a newly created\nbranch should be configured to automatically rebase by default.\nMove it into 'struct repo_config_values' to continue the\nlibification effort.\n\nThe 'enum rebase_setup_type' definition is moved higher up in\n'environment.h' so that it is visible to the repository-specific\nstructure. The default state AUTOREBASE_NEVER is now correctly\ninitialized in 'repo_config_values_init()'.\n\nConfiguration parsing in 'git_default_branch_config()' is updated to\nwrite directly to the repository's configuration instance.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n branch.c      |  2 +-\n environment.c | 10 +++++-----\n environment.h | 16 ++++++++--------\n 3 files changed, 14 insertions(+), 14 deletions(-)\n\ndiff --git a/branch.c b/branch.c\nindex 243db7d0fc..e1c1f8c89d 100644\n--- a/branch.c\n+++ b/branch.c\n@@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv)\n \n static int should_setup_rebase(const char *origin)\n {\n-\tswitch (autorebase) {\n+\tswitch (repo_config_values(the_repository)->autorebase) {\n \tcase AUTOREBASE_NEVER:\n \t\treturn 0;\n \tcase AUTOREBASE_LOCAL:\ndiff --git a/environment.c b/environment.c\nindex 66c1ac1ab8..c0bf7577b7 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -57,7 +57,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE;\n enum eol core_eol = EOL_UNSET;\n int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN;\n char *check_roundtrip_encoding;\n-enum rebase_setup_type autorebase = AUTOREBASE_NEVER;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n@@ -601,13 +600,13 @@ static int git_default_branch_config(const char *var, const char *value)\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\telse if (!strcmp(value, \"never\"))\n-\t\t\tautorebase = AUTOREBASE_NEVER;\n+\t\t\tcfg->autorebase = AUTOREBASE_NEVER;\n \t\telse if (!strcmp(value, \"local\"))\n-\t\t\tautorebase = AUTOREBASE_LOCAL;\n+\t\t\tcfg->autorebase = AUTOREBASE_LOCAL;\n \t\telse if (!strcmp(value, \"remote\"))\n-\t\t\tautorebase = AUTOREBASE_REMOTE;\n+\t\t\tcfg->autorebase = AUTOREBASE_REMOTE;\n \t\telse if (!strcmp(value, \"always\"))\n-\t\t\tautorebase = AUTOREBASE_ALWAYS;\n+\t\t\tcfg->autorebase = AUTOREBASE_ALWAYS;\n \t\telse\n \t\t\treturn error(_(\"malformed value for %s\"), var);\n \t\treturn 0;\n@@ -728,6 +727,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_whitespace = NULL;\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n+\tcfg->autorebase = AUTOREBASE_NEVER;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 17a3a628d2..46b2f0d861 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -102,6 +102,13 @@ enum push_default_type {\n \tPUSH_DEFAULT_UNSPECIFIED\n };\n \n+enum rebase_setup_type {\n+\tAUTOREBASE_NEVER = 0,\n+\tAUTOREBASE_LOCAL,\n+\tAUTOREBASE_REMOTE,\n+\tAUTOREBASE_ALWAYS\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -112,6 +119,7 @@ struct repo_config_values {\n \tchar *apply_default_whitespace;\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n+\tenum rebase_setup_type autorebase;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -205,14 +213,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum rebase_setup_type {\n-\tAUTOREBASE_NEVER = 0,\n-\tAUTOREBASE_LOCAL,\n-\tAUTOREBASE_REMOTE,\n-\tAUTOREBASE_ALWAYS\n-};\n-extern enum rebase_setup_type autorebase;\n-\n enum object_creation_mode {\n \tOBJECT_CREATION_USES_HARDLINKS = 0,\n \tOBJECT_CREATION_USES_RENAMES = 1\n-- \n2.43.0\n\n"},{"id":"548065","messageId":"20260714032525.1611141-10-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 09/10] environment: move object_creation_mode into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:24Z","receivedAt":"2026-07-14T03:26:04Z","isPatch":true,"body":"The global variable 'object_creation_mode' controls how Git creates\nobject files, specifically determining whether to use hardlinks or\nrenames when moving temporary files into the object database. Move\nit into 'struct repo_config_values' to continue the libification\neffort.\n\nMove the 'enum object_creation_mode' definition higher up in\n'environment.h' to ensure it is visible to the structure. Initialize\nthe per-repository value to its default macro value\nOBJECT_CREATION_MODE inside 'repo_config_values_init()'.\n\nUpdate configuration parsing in 'git_default_core_config()' to write\ndirectly to the repository-specific configuration structure.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n environment.c |  6 +++---\n environment.h | 12 ++++++------\n object-file.c |  3 ++-\n 3 files changed, 11 insertions(+), 10 deletions(-)\n\ndiff --git a/environment.c b/environment.c\nindex c0bf7577b7..ef3c032e0c 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -60,7 +60,6 @@ char *check_roundtrip_encoding;\n #ifndef OBJECT_CREATION_MODE\n #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS\n #endif\n-enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE;\n int grafts_keep_true_parents;\n unsigned long pack_size_limit_cfg;\n \n@@ -512,9 +511,9 @@ int git_default_core_config(const char *var, const char *value,\n \t\tif (!value)\n \t\t\treturn config_error_nonbool(var);\n \t\tif (!strcmp(value, \"rename\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_RENAMES;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES;\n \t\telse if (!strcmp(value, \"link\"))\n-\t\t\tobject_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n+\t\t\tcfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS;\n \t\telse\n \t\t\tdie(_(\"invalid mode for object creation: %s\"), value);\n \t\treturn 0;\n@@ -728,6 +727,7 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->apply_default_ignorewhitespace = NULL;\n \tcfg->push_default = PUSH_DEFAULT_UNSPECIFIED;\n \tcfg->autorebase = AUTOREBASE_NEVER;\n+\tcfg->object_creation_mode = OBJECT_CREATION_MODE;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n \tcfg->trust_ctime = 1;\ndiff --git a/environment.h b/environment.h\nindex 46b2f0d861..a47a5c83db 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -109,6 +109,11 @@ enum rebase_setup_type {\n \tAUTOREBASE_ALWAYS\n };\n \n+enum object_creation_mode {\n+\tOBJECT_CREATION_USES_HARDLINKS = 0,\n+\tOBJECT_CREATION_USES_RENAMES = 1\n+};\n+\n struct repo_config_values {\n \t/* section \"core\" config values */\n \tchar *attributes_file;\n@@ -120,6 +125,7 @@ struct repo_config_values {\n \tchar *apply_default_ignorewhitespace;\n \tenum push_default_type push_default;\n \tenum rebase_setup_type autorebase;\n+\tenum object_creation_mode object_creation_mode;\n \tint apply_sparse_checkout;\n \tint trust_ctime;\n \tint check_stat;\n@@ -213,12 +219,6 @@ extern unsigned long pack_size_limit_cfg;\n extern int protect_hfs;\n extern int protect_ntfs;\n \n-enum object_creation_mode {\n-\tOBJECT_CREATION_USES_HARDLINKS = 0,\n-\tOBJECT_CREATION_USES_RENAMES = 1\n-};\n-extern enum object_creation_mode object_creation_mode;\n-\n extern int grafts_keep_true_parents;\n \n const char *get_log_output_encoding(void);\ndiff --git a/object-file.c b/object-file.c\nindex 9afa842da2..c00dd3afca 100644\n--- a/object-file.c\n+++ b/object-file.c\n@@ -411,11 +411,12 @@ int finalize_object_file_flags(struct repository *repo,\n {\n \tunsigned retries = 0;\n \tint ret;\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n \n retry:\n \tret = 0;\n \n-\tif (object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n+\tif (cfg->object_creation_mode == OBJECT_CREATION_USES_RENAMES)\n \t\tgoto try_rename;\n \telse if (link(tmpfile, filename))\n \t\tret = errno;\n-- \n2.43.0\n\n"},{"id":"548066","messageId":"20260714032525.1611141-11-cat@malon.dev","threadId":"65873","inReplyTo":"20260714032525.1611141-1-cat@malon.dev","subject":"[PATCH v12 10/10] repository: adjust the comment of config_values_private_","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-14T03:25:25Z","receivedAt":"2026-07-14T03:26:08Z","isPatch":true,"body":"The configurations in 'struct config_values_private_' are not all\nparsed in 'git_default_config()'. For example, 'pager_program' is\nnow parsed in 'pager.c'. Therefore, update the comment.\n\nMentored-by: Christian Couder <christian.couder@gmail.com>\nMentored-by: Ayush Chandekar <ayu.chandekar@gmail.com>\nMentored-by: Olamide Caleb Bello <belkid98@gmail.com>\nSigned-off-by: Tian Yuchen <cat@malon.dev>\n---\n repository.h | 2 +-\n 1 file changed, 1 insertion(+), 1 deletion(-)\n\ndiff --git a/repository.h b/repository.h\nindex 36e2db2633..9093e6af93 100644\n--- a/repository.h\n+++ b/repository.h\n@@ -152,7 +152,7 @@ struct repository {\n \t/* Repository's compatibility hash algorithm. */\n \tconst struct git_hash_algo *compat_hash_algo;\n \n-\t/* Repository's config values parsed by git_default_config() */\n+\t/* Repository-specific configuration values. */\n \tstruct repo_config_values config_values_private_;\n \n \t/* Repository's reference storage format, as serialized on disk. */\n-- \n2.43.0\n\n"},{"id":"548133","messageId":"xmqqcxwpv8rv.fsf@gitster.g","threadId":"65873","inReplyTo":"61ef1b0a-72e8-45b3-b6e8-46eb5b34ea91@malon.dev","subject":"Re: [PATCH v11 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-14T16:24:52Z","receivedAt":"2026-07-14T16:24:56Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> On 7/14/26 00:39, Junio C Hamano wrote:\n>> Tian Yuchen <cat@malon.dev> writes:\n>> \n>>> Subject: Re: [PATCH v11 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace\n>> \n>> Are there patches 7..10/10 posted somewhere else?  I didn't see them\n>> in the thread (neither did \"b4\").\n>> \n>\n> Oh, I didn't notice that:\n>\n> \tDied at /usr/lib/git-core/git-send-email line 1665.\n>\n> Will resend very soon.\n\nThanks.\n"}]}