From 0201d2783e6317c7b76e9c511cbfcfa73fdc17b1 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:16 +0800 Subject: [PATCH 01/10] repository: introduce repo_config_values_clear() As part of the ongoing libification effort, dynamically allocated global configuration variables are being moved into 'struct repo_config_values'. To prevent memory leaks, we need a destructor to free these heap-allocated variables when a repository instance is torn down. Introduce 'repo_config_values_clear()' in environment.c and invoke it from 'repo_clear()' in repository.c. As a starting point, update this new function to handle the cleanup of 'attributes_file'. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- environment.c | 5 +++++ environment.h | 9 +++++++++ repository.c | 1 + 3 files changed, 15 insertions(+) diff --git a/environment.c b/environment.c index ba2c60103f..ae05f16d04 100644 --- a/environment.c +++ b/environment.c @@ -726,3 +726,8 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->sparse_expect_files_outside_of_patterns = 0; cfg->warn_on_object_refname_ambiguity = 1; } + +void repo_config_values_clear(struct repo_config_values *cfg) +{ + FREE_AND_NULL(cfg->attributes_file); +} diff --git a/environment.h b/environment.h index 6f18286955..9169d7f62d 100644 --- a/environment.h +++ b/environment.h @@ -135,6 +135,15 @@ int git_default_core_config(const char *var, const char *value, void repo_config_values_init(struct repo_config_values *cfg); +/* + * Frees memory allocated for dynamically loaded configuration values + * inside `repo_config_values`. + * + * As dynamically allocated variables are migrated into this struct, + * their FREE_AND_NULL() calls should be appended here. + */ +void repo_config_values_clear(struct repo_config_values *cfg); + /* * TODO: All the below state either explicitly or implicitly relies on * `the_repository`. We should eventually get rid of these and make the diff --git a/repository.c b/repository.c index 187dd471c4..669e2d1200 100644 --- a/repository.c +++ b/repository.c @@ -388,6 +388,7 @@ void repo_clear(struct repository *repo) FREE_AND_NULL(repo->parsed_objects); repo_settings_clear(repo); + repo_config_values_clear(&repo->config_values_private_); if (repo->config) { git_configset_clear(repo->config); From 7488b9d1fd505ce1194b98893508fa5b194cf0cd Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:17 +0800 Subject: [PATCH 02/10] environment: move excludes_file into repo_config_values The global variable 'excludes_file' is used to track the path to the global ignore file. If this variable is NULL, 'setup_standard_excludes()' in 'dir.c' forcefully evaluates and assigns the XDG default path to it. Continue the libification effort by encapsulating this lazy-loading fallback logic into a proper getter and moving the variable into 'struct repo_config_values'. Since 'excludes_file' is a dynamically allocated string, it requires proper heap memory management. It is safely freed using the newly introduced 'repo_config_values_clear()' function when the repository is torn down. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- dir.c | 4 ++-- environment.c | 17 ++++++++++++++--- environment.h | 4 +++- 3 files changed, 19 insertions(+), 6 deletions(-) diff --git a/dir.c b/dir.c index 32430090dc..baaf899d9c 100644 --- a/dir.c +++ b/dir.c @@ -3481,11 +3481,11 @@ static GIT_PATH_FUNC(git_path_info_exclude, "info/exclude") void setup_standard_excludes(struct dir_struct *dir) { + const char *excludes_file = repo_excludes_file(the_repository); + dir->exclude_per_dir = ".gitignore"; /* core.excludesfile defaulting to $XDG_CONFIG_HOME/git/ignore */ - if (!excludes_file) - excludes_file = xdg_config_home("ignore"); if (excludes_file && !access_or_warn(excludes_file, R_OK, 0)) add_patterns_from_file_1(dir, excludes_file, dir->untracked ? &dir->internal.ss_excludes_file : NULL); diff --git a/environment.c b/environment.c index ae05f16d04..275931c213 100644 --- a/environment.c +++ b/environment.c @@ -57,7 +57,6 @@ enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT; enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT; char *editor_program; char *askpass_program; -char *excludes_file; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; @@ -134,6 +133,16 @@ int is_bare_repository(void) return is_bare_repository_cfg && !repo_get_work_tree(the_repository); } +const char *repo_excludes_file(struct repository *repo) +{ + struct repo_config_values *cfg = repo_config_values(repo); + + if (!cfg->excludes_file) + cfg->excludes_file = xdg_config_home("ignore"); + + return cfg->excludes_file; +} + int have_git_dir(void) { return startup_info->have_repository @@ -461,8 +470,8 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.excludesfile")) { - FREE_AND_NULL(excludes_file); - return git_config_pathname(&excludes_file, var, value); + FREE_AND_NULL(cfg->excludes_file); + return git_config_pathname(&cfg->excludes_file, var, value); } if (!strcmp(var, "core.whitespace")) { @@ -715,6 +724,7 @@ int git_default_config(const char *var, const char *value, void repo_config_values_init(struct repo_config_values *cfg) { cfg->attributes_file = NULL; + cfg->excludes_file = NULL; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; @@ -730,4 +740,5 @@ void repo_config_values_init(struct repo_config_values *cfg) void repo_config_values_clear(struct repo_config_values *cfg) { FREE_AND_NULL(cfg->attributes_file); + FREE_AND_NULL(cfg->excludes_file); } diff --git a/environment.h b/environment.h index 9169d7f62d..4776ccc657 100644 --- a/environment.h +++ b/environment.h @@ -90,6 +90,7 @@ struct repository; struct repo_config_values { /* section "core" config values */ char *attributes_file; + char *excludes_file; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -133,6 +134,8 @@ int git_default_config(const char *, const char *, int git_default_core_config(const char *var, const char *value, const struct config_context *ctx, void *cb); +const char *repo_excludes_file(struct repository *repo); + void repo_config_values_init(struct repo_config_values *cfg); /* @@ -217,7 +220,6 @@ extern char *git_log_output_encoding; extern char *editor_program; extern char *askpass_program; -extern char *excludes_file; /* * The character that begins a commented line in user-editable file From 469e95c2573e938c0963ba52e8d06dd6aa7ba262 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:18 +0800 Subject: [PATCH 03/10] environment: move editor_program into repo_config_values The global variable 'editor_program' holds the path to the user's preferred editor. Move 'editor_program' into 'struct repo_config_values' to continue the libification effort. There have been discussions on whether external programs like editors truly need to be configured on a per-repository basis within the same process. While a single process might rarely invoke different editors, this migration is necessary for two reasons: 1. Developers frequently use different toolchains for different projects. Per-repo configuration respects this. 2. Moving this string into 'repo_config_values' eliminates mutable global state. As the codebase moves toward becoming a long-running processes, managing multiple repositories concurrently must not overwrite each other's program configurations. No standalone getter function is introduced. Callers directly access the field via 'repo_config_values()'. Heap memory is safely reclaimed in 'repo_config_values_clear()'. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- editor.c | 4 ++-- environment.c | 7 ++++--- environment.h | 2 +- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/editor.c b/editor.c index fd174e6a03..0d1cb8768d 100644 --- a/editor.c +++ b/editor.c @@ -29,8 +29,8 @@ const char *git_editor(void) const char *editor = getenv("GIT_EDITOR"); int terminal_is_dumb = is_terminal_dumb(); - if (!editor && editor_program) - editor = editor_program; + if (!editor) + editor = repo_config_values(the_repository)->editor_program; if (!editor && !terminal_is_dumb) editor = getenv("VISUAL"); if (!editor) diff --git a/environment.c b/environment.c index 275931c213..a65d575af4 100644 --- a/environment.c +++ b/environment.c @@ -55,7 +55,6 @@ int fsync_object_files = -1; int use_fsync = -1; enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT; enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT; -char *editor_program; char *askpass_program; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; @@ -437,8 +436,8 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.editor")) { - FREE_AND_NULL(editor_program); - return git_config_string(&editor_program, var, value); + FREE_AND_NULL(cfg->editor_program); + return git_config_string(&cfg->editor_program, var, value); } if (!strcmp(var, "core.commentchar") || @@ -725,6 +724,7 @@ void repo_config_values_init(struct repo_config_values *cfg) { cfg->attributes_file = NULL; cfg->excludes_file = NULL; + cfg->editor_program = NULL; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; @@ -741,4 +741,5 @@ void repo_config_values_clear(struct repo_config_values *cfg) { FREE_AND_NULL(cfg->attributes_file); FREE_AND_NULL(cfg->excludes_file); + FREE_AND_NULL(cfg->editor_program); } diff --git a/environment.h b/environment.h index 4776ccc657..8178ebab76 100644 --- a/environment.h +++ b/environment.h @@ -91,6 +91,7 @@ struct repo_config_values { /* section "core" config values */ char *attributes_file; char *excludes_file; + char *editor_program; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -218,7 +219,6 @@ const char *get_commit_output_encoding(void); extern char *git_commit_encoding; extern char *git_log_output_encoding; -extern char *editor_program; extern char *askpass_program; /* From e57125d804f29b8a6c70a47a03037cf31ef153d7 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:19 +0800 Subject: [PATCH 04/10] environment: move pager_program into repo_config_values The 'pager_program' variable is currently defined as a file-scoped static string in pager.c. Move it into 'struct repo_config_values'. The configuration parsing logic remains strictly within pager.c to respect subsystem boundaries. The read/write operations are simply redirected to the repository-specific structure using 'repo_config_values()'. All current callers indeed pass 'the_repository', so this new enforcement does not harm them. Similar to the recent editor_program migration, no standalone getter is introduced to keep the code minimal. The dynamically allocated memory is now managed by 'repo_config_values_clear()'. On top of that, fix memory leaks in pager.c while we are at it. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- environment.c | 2 ++ environment.h | 1 + pager.c | 32 +++++++++++++++++++++++--------- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/environment.c b/environment.c index a65d575af4..975c9cb9eb 100644 --- a/environment.c +++ b/environment.c @@ -725,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->attributes_file = NULL; cfg->excludes_file = NULL; cfg->editor_program = NULL; + cfg->pager_program = NULL; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; @@ -742,4 +743,5 @@ void repo_config_values_clear(struct repo_config_values *cfg) FREE_AND_NULL(cfg->attributes_file); FREE_AND_NULL(cfg->excludes_file); FREE_AND_NULL(cfg->editor_program); + FREE_AND_NULL(cfg->pager_program); } diff --git a/environment.h b/environment.h index 8178ebab76..39b6691b47 100644 --- a/environment.h +++ b/environment.h @@ -92,6 +92,7 @@ struct repo_config_values { char *attributes_file; char *excludes_file; char *editor_program; + char *pager_program; int apply_sparse_checkout; int trust_ctime; int check_stat; diff --git a/pager.c b/pager.c index 35b210e048..543ef12936 100644 --- a/pager.c +++ b/pager.c @@ -5,6 +5,8 @@ #include "run-command.h" #include "sigchain.h" #include "alias.h" +#include "repository.h" +#include "environment.h" int pager_use_color = 1; @@ -13,7 +15,6 @@ int pager_use_color = 1; #endif static struct child_process pager_process; -static char *pager_program; static int old_fd1 = -1, old_fd2 = -1; /* Is the value coming back from term_columns() just a guess? */ @@ -75,10 +76,17 @@ static void wait_for_pager_signal(int signo) static int core_pager_config(const char *var, const char *value, const struct config_context *ctx UNUSED, - void *data UNUSED) + void *data) { - if (!strcmp(var, "core.pager")) - return git_config_string(&pager_program, var, value); + struct repository *r = data; + + if (!strcmp(var, "core.pager")) { + struct repo_config_values *cfg = repo_config_values(r); + + FREE_AND_NULL(cfg->pager_program); + return git_config_string(&cfg->pager_program, var, value); + } + return 0; } @@ -91,10 +99,12 @@ const char *git_pager(struct repository *r, int stdout_is_tty) pager = getenv("GIT_PAGER"); if (!pager) { - if (!pager_program) + struct repo_config_values *cfg = repo_config_values(r); + + if (!cfg->pager_program) read_early_config(r, - core_pager_config, NULL); - pager = pager_program; + core_pager_config, r); + pager = cfg->pager_program; } if (!pager) pager = getenv("PAGER"); @@ -302,7 +312,11 @@ int check_pager_config(struct repository *r, const char *cmd) read_early_config(r, pager_command_config, &data); - if (data.value) - pager_program = data.value; + if (data.value) { + struct repo_config_values *cfg = repo_config_values(r); + + free(cfg->pager_program); + cfg->pager_program = data.value; + } return data.want; } From 48cbe400794bd055d259143a01024da71da5fe1a Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:20 +0800 Subject: [PATCH 05/10] environment: move askpass_program into repo_config_values The global variable 'askpass_program' stores the path to the program used to prompt the user for credentials. Move it into repo_config_values to continue the libification effort. While it is uncommon for a single process to require different askpass programs for different repositories, maintaining this value as a mutable global string is a blocker for libification. Global heap-allocated strings introduce thread-safety issues in a multi-repo environment. Move 'askpass_program' into 'struct repo_config_values' to eliminate this global state. The memory is now safely managed and freed via 'repo_config_values_clear()'. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- environment.c | 7 ++++--- environment.h | 3 +-- prompt.c | 3 ++- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/environment.c b/environment.c index 975c9cb9eb..3857818da3 100644 --- a/environment.c +++ b/environment.c @@ -55,7 +55,6 @@ int fsync_object_files = -1; int use_fsync = -1; enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT; enum fsync_component fsync_components = FSYNC_COMPONENTS_DEFAULT; -char *askpass_program; enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; @@ -464,8 +463,8 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.askpass")) { - FREE_AND_NULL(askpass_program); - return git_config_string(&askpass_program, var, value); + FREE_AND_NULL(cfg->askpass_program); + return git_config_string(&cfg->askpass_program, var, value); } if (!strcmp(var, "core.excludesfile")) { @@ -726,6 +725,7 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->excludes_file = NULL; cfg->editor_program = NULL; cfg->pager_program = NULL; + cfg->askpass_program = NULL; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; @@ -744,4 +744,5 @@ void repo_config_values_clear(struct repo_config_values *cfg) FREE_AND_NULL(cfg->excludes_file); FREE_AND_NULL(cfg->editor_program); FREE_AND_NULL(cfg->pager_program); + FREE_AND_NULL(cfg->askpass_program); } diff --git a/environment.h b/environment.h index 39b6691b47..856dc70cc4 100644 --- a/environment.h +++ b/environment.h @@ -93,6 +93,7 @@ struct repo_config_values { char *excludes_file; char *editor_program; char *pager_program; + char *askpass_program; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -220,8 +221,6 @@ const char *get_commit_output_encoding(void); extern char *git_commit_encoding; extern char *git_log_output_encoding; -extern char *askpass_program; - /* * The character that begins a commented line in user-editable file * that is subject to stripspace. diff --git a/prompt.c b/prompt.c index 706fba2a50..d8d74c7e37 100644 --- a/prompt.c +++ b/prompt.c @@ -3,6 +3,7 @@ #include "git-compat-util.h" #include "parse.h" #include "environment.h" +#include "repository.h" #include "run-command.h" #include "strbuf.h" #include "prompt.h" @@ -51,7 +52,7 @@ char *git_prompt(const char *prompt, int flags) askpass = getenv("GIT_ASKPASS"); if (!askpass) - askpass = askpass_program; + askpass = repo_config_values(the_repository)->askpass_program; if (!askpass) askpass = getenv("SSH_ASKPASS"); if (askpass && *askpass) From a9f5e90fb9068fd1a1a9a7c9a60f005e7ed392dd Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:21 +0800 Subject: [PATCH 06/10] environment: migrate apply_default_whitespace and apply_default_ignorewhitespace The global variables 'apply_default_whitespace' and 'apply_default_ignorewhitespace' are used to store the default whitespace configuration for 'git apply'. Move these variables into 'struct repo_config_values' to continue the libification effort. Dynamically allocated strings fetched via 'repo_config_get_string()' are now tracked per-repository and safely freed in 'repo_config_values_clear()'. As part of this transition, update 'git_apply_config()' to accept a 'struct repository *' argument rather than relying on the 'the_repository' global. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- apply.c | 28 ++++++++++++++++++++-------- environment.c | 6 ++++-- environment.h | 4 ++-- 3 files changed, 26 insertions(+), 12 deletions(-) diff --git a/apply.c b/apply.c index 5e54453f79..7e5b12323e 100644 --- a/apply.c +++ b/apply.c @@ -47,11 +47,17 @@ struct gitdiff_data { int p_value; }; -static void git_apply_config(void) +static void git_apply_config(struct repository *repo) { - repo_config_get_string(the_repository, "apply.whitespace", &apply_default_whitespace); - repo_config_get_string(the_repository, "apply.ignorewhitespace", &apply_default_ignorewhitespace); - repo_config(the_repository, git_xmerge_config, NULL); + struct repo_config_values *cfg = repo_config_values(repo); + + FREE_AND_NULL(cfg->apply_default_whitespace); + repo_config_get_string(repo, "apply.whitespace", + &cfg->apply_default_whitespace); + FREE_AND_NULL(cfg->apply_default_ignorewhitespace); + repo_config_get_string(repo, "apply.ignorewhitespace", + &cfg->apply_default_ignorewhitespace); + repo_config(repo, git_xmerge_config, NULL); } static int parse_whitespace_option(struct apply_state *state, const char *option) @@ -109,6 +115,8 @@ int init_apply_state(struct apply_state *state, struct repository *repo, const char *prefix) { + struct repo_config_values *cfg = repo_config_values(repo); + memset(state, 0, sizeof(*state)); state->prefix = prefix; state->repo = repo; @@ -126,10 +134,13 @@ int init_apply_state(struct apply_state *state, strset_init(&state->kept_symlinks); strbuf_init(&state->root, 0); - git_apply_config(); - if (apply_default_whitespace && parse_whitespace_option(state, apply_default_whitespace)) + git_apply_config(repo); + + if (cfg->apply_default_whitespace && + parse_whitespace_option(state, cfg->apply_default_whitespace)) return -1; - if (apply_default_ignorewhitespace && parse_ignorewhitespace_option(state, apply_default_ignorewhitespace)) + if (cfg->apply_default_ignorewhitespace && + parse_ignorewhitespace_option(state, cfg->apply_default_ignorewhitespace)) return -1; return 0; } @@ -192,7 +203,8 @@ int check_apply_state(struct apply_state *state, int force_apply) static void set_default_whitespace_mode(struct apply_state *state) { - if (!state->whitespace_option && !apply_default_whitespace) + if (!state->whitespace_option && + !repo_config_values(state->repo)->apply_default_whitespace) state->ws_error_action = (state->apply ? warn_on_ws_error : nowarn_ws_error); } diff --git a/environment.c b/environment.c index 3857818da3..20500658a2 100644 --- a/environment.c +++ b/environment.c @@ -49,8 +49,6 @@ int assume_unchanged; int is_bare_repository_cfg = -1; /* unspecified */ char *git_commit_encoding; char *git_log_output_encoding; -char *apply_default_whitespace; -char *apply_default_ignorewhitespace; int fsync_object_files = -1; int use_fsync = -1; enum fsync_method fsync_method = FSYNC_METHOD_DEFAULT; @@ -726,6 +724,8 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->editor_program = NULL; cfg->pager_program = NULL; cfg->askpass_program = NULL; + cfg->apply_default_whitespace = NULL; + cfg->apply_default_ignorewhitespace = NULL; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; @@ -745,4 +745,6 @@ void repo_config_values_clear(struct repo_config_values *cfg) FREE_AND_NULL(cfg->editor_program); FREE_AND_NULL(cfg->pager_program); FREE_AND_NULL(cfg->askpass_program); + FREE_AND_NULL(cfg->apply_default_whitespace); + FREE_AND_NULL(cfg->apply_default_ignorewhitespace); } diff --git a/environment.h b/environment.h index 856dc70cc4..f450242ac0 100644 --- a/environment.h +++ b/environment.h @@ -94,6 +94,8 @@ struct repo_config_values { char *editor_program; char *pager_program; char *askpass_program; + char *apply_default_whitespace; + char *apply_default_ignorewhitespace; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -182,8 +184,6 @@ extern int has_symlinks; extern int minimum_abbrev, default_abbrev; extern int ignore_case; extern int assume_unchanged; -extern char *apply_default_whitespace; -extern char *apply_default_ignorewhitespace; extern unsigned long pack_size_limit_cfg; extern int protect_hfs; From 1a6c84e98dffe06fbe1acdf03817dffa10e180ef Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:22 +0800 Subject: [PATCH 07/10] environment: move push_default into repo_config_values The global variable 'push_default' specifies the default behavior of 'git push' when no explicit refspec is provided. Move 'push_default' into 'struct repo_config_values' to continue the libification effort. While 'enum push_default_type' ideally belongs in 'remote.h', moving it there introduces a circular dependency chain: remote.h -> hash.h -> repository.h -> environment.h. Therefore, the enum definition is kept in 'environment.h' just above 'struct repo_config_values' with a NEEDSWORK comment for future cleanup. Modify the configuration parsing in environment.c to update the per-repository structure directly, and update caller across the codebase to access the value via 'repo_config_values()'. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- builtin/push.c | 10 ++++++---- environment.c | 16 +++++++++------- environment.h | 26 ++++++++++++++++---------- remote.c | 2 +- 4 files changed, 32 insertions(+), 22 deletions(-) diff --git a/builtin/push.c b/builtin/push.c index 6021b71d66..7578ff38c4 100644 --- a/builtin/push.c +++ b/builtin/push.c @@ -73,6 +73,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref, struct remote *remote, struct ref *matched) { const char *branch_name; + struct repo_config_values *cfg = repo_config_values(the_repository); if (remote->push.nr) { struct refspec_item query = { @@ -88,7 +89,7 @@ static void refspec_append_mapped(struct refspec *refspec, const char *ref, } } - if (push_default == PUSH_DEFAULT_UPSTREAM && + if (cfg->push_default == PUSH_DEFAULT_UPSTREAM && skip_prefix(matched->name, "refs/heads/", &branch_name)) { struct branch *branch = branch_get(branch_name); if (branch->merge_nr == 1 && branch->merge[0]->src) { @@ -160,7 +161,7 @@ static NORETURN void die_push_simple(struct branch *branch, * Don't show advice for people who explicitly set * push.default. */ - if (push_default == PUSH_DEFAULT_UNSPECIFIED) + if (cfg->push_default == PUSH_DEFAULT_UNSPECIFIED) advice_pushdefault_maybe = _("\n" "To choose either option permanently, " "see push.default in 'git help config'.\n"); @@ -231,8 +232,9 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote) struct branch *branch; const char *dst; int same_remote; + struct repo_config_values *cfg = repo_config_values(the_repository); - switch (push_default) { + switch (cfg->push_default) { case PUSH_DEFAULT_MATCHING: refspec_append(&rs, ":"); return; @@ -252,7 +254,7 @@ static void setup_default_push_refspecs(int *flags, struct remote *remote) dst = branch->refname; same_remote = !strcmp(remote->name, remote_for_branch(branch, NULL)); - switch (push_default) { + switch (cfg->push_default) { default: case PUSH_DEFAULT_UNSPECIFIED: case PUSH_DEFAULT_SIMPLE: diff --git a/environment.c b/environment.c index 20500658a2..66c1ac1ab8 100644 --- a/environment.c +++ b/environment.c @@ -58,7 +58,6 @@ enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; char *check_roundtrip_encoding; enum rebase_setup_type autorebase = AUTOREBASE_NEVER; -enum push_default_type push_default = PUSH_DEFAULT_UNSPECIFIED; #ifndef OBJECT_CREATION_MODE #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif @@ -620,21 +619,23 @@ static int git_default_branch_config(const char *var, const char *value) static int git_default_push_config(const char *var, const char *value) { + struct repo_config_values *cfg = repo_config_values(the_repository); + if (!strcmp(var, "push.default")) { if (!value) return config_error_nonbool(var); else if (!strcmp(value, "nothing")) - push_default = PUSH_DEFAULT_NOTHING; + cfg->push_default = PUSH_DEFAULT_NOTHING; else if (!strcmp(value, "matching")) - push_default = PUSH_DEFAULT_MATCHING; + cfg->push_default = PUSH_DEFAULT_MATCHING; else if (!strcmp(value, "simple")) - push_default = PUSH_DEFAULT_SIMPLE; + cfg->push_default = PUSH_DEFAULT_SIMPLE; else if (!strcmp(value, "upstream")) - push_default = PUSH_DEFAULT_UPSTREAM; + cfg->push_default = PUSH_DEFAULT_UPSTREAM; else if (!strcmp(value, "tracking")) /* deprecated */ - push_default = PUSH_DEFAULT_UPSTREAM; + cfg->push_default = PUSH_DEFAULT_UPSTREAM; else if (!strcmp(value, "current")) - push_default = PUSH_DEFAULT_CURRENT; + cfg->push_default = PUSH_DEFAULT_CURRENT; else { error(_("malformed value for %s: %s"), var, value); return error(_("must be one of nothing, matching, simple, " @@ -726,6 +727,7 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->askpass_program = NULL; cfg->apply_default_whitespace = NULL; cfg->apply_default_ignorewhitespace = NULL; + cfg->push_default = PUSH_DEFAULT_UNSPECIFIED; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; diff --git a/environment.h b/environment.h index f450242ac0..17a3a628d2 100644 --- a/environment.h +++ b/environment.h @@ -87,6 +87,21 @@ extern const char * const local_repo_env[]; struct strvec; struct repository; + +/* + * NEEDSWORK: It would be better if these definitions could be moved to + * other more specific files, but care is needed to avoid circular + * inclusion issues. + */ +enum push_default_type { + PUSH_DEFAULT_NOTHING = 0, + PUSH_DEFAULT_MATCHING, + PUSH_DEFAULT_SIMPLE, + PUSH_DEFAULT_UPSTREAM, + PUSH_DEFAULT_CURRENT, + PUSH_DEFAULT_UNSPECIFIED +}; + struct repo_config_values { /* section "core" config values */ char *attributes_file; @@ -96,6 +111,7 @@ struct repo_config_values { char *askpass_program; char *apply_default_whitespace; char *apply_default_ignorewhitespace; + enum push_default_type push_default; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -197,16 +213,6 @@ enum rebase_setup_type { }; extern enum rebase_setup_type autorebase; -enum push_default_type { - PUSH_DEFAULT_NOTHING = 0, - PUSH_DEFAULT_MATCHING, - PUSH_DEFAULT_SIMPLE, - PUSH_DEFAULT_UPSTREAM, - PUSH_DEFAULT_CURRENT, - PUSH_DEFAULT_UNSPECIFIED -}; -extern enum push_default_type push_default; - enum object_creation_mode { OBJECT_CREATION_USES_HARDLINKS = 0, OBJECT_CREATION_USES_RENAMES = 1 diff --git a/remote.c b/remote.c index 00723b385e..d48c01d375 100644 --- a/remote.c +++ b/remote.c @@ -1933,7 +1933,7 @@ static char *branch_get_push_1(struct repository *repo, if (remote->mirror) return tracking_for_push_dest(remote, branch->refname, err); - switch (push_default) { + switch (repo_config_values(repo)->push_default) { case PUSH_DEFAULT_NOTHING: return error_buf(err, _("push has no destination (push.default is 'nothing')")); From 1840ca005fbabc651f9018d88f0f9d8adb91b015 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:23 +0800 Subject: [PATCH 08/10] environment: move autorebase into repo_config_values The global variable 'autorebase' dictates whether a newly created branch should be configured to automatically rebase by default. Move it into 'struct repo_config_values' to continue the libification effort. The 'enum rebase_setup_type' definition is moved higher up in 'environment.h' so that it is visible to the repository-specific structure. The default state AUTOREBASE_NEVER is now correctly initialized in 'repo_config_values_init()'. Configuration parsing in 'git_default_branch_config()' is updated to write directly to the repository's configuration instance. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- branch.c | 2 +- environment.c | 10 +++++----- environment.h | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/branch.c b/branch.c index 243db7d0fc..e1c1f8c89d 100644 --- a/branch.c +++ b/branch.c @@ -61,7 +61,7 @@ static int find_tracked_branch(struct remote *remote, void *priv) static int should_setup_rebase(const char *origin) { - switch (autorebase) { + switch (repo_config_values(the_repository)->autorebase) { case AUTOREBASE_NEVER: return 0; case AUTOREBASE_LOCAL: diff --git a/environment.c b/environment.c index 66c1ac1ab8..c0bf7577b7 100644 --- a/environment.c +++ b/environment.c @@ -57,7 +57,6 @@ enum auto_crlf auto_crlf = AUTO_CRLF_FALSE; enum eol core_eol = EOL_UNSET; int global_conv_flags_eol = CONV_EOL_RNDTRP_WARN; char *check_roundtrip_encoding; -enum rebase_setup_type autorebase = AUTOREBASE_NEVER; #ifndef OBJECT_CREATION_MODE #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif @@ -601,13 +600,13 @@ static int git_default_branch_config(const char *var, const char *value) if (!value) return config_error_nonbool(var); else if (!strcmp(value, "never")) - autorebase = AUTOREBASE_NEVER; + cfg->autorebase = AUTOREBASE_NEVER; else if (!strcmp(value, "local")) - autorebase = AUTOREBASE_LOCAL; + cfg->autorebase = AUTOREBASE_LOCAL; else if (!strcmp(value, "remote")) - autorebase = AUTOREBASE_REMOTE; + cfg->autorebase = AUTOREBASE_REMOTE; else if (!strcmp(value, "always")) - autorebase = AUTOREBASE_ALWAYS; + cfg->autorebase = AUTOREBASE_ALWAYS; else return error(_("malformed value for %s"), var); return 0; @@ -728,6 +727,7 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->apply_default_whitespace = NULL; cfg->apply_default_ignorewhitespace = NULL; cfg->push_default = PUSH_DEFAULT_UNSPECIFIED; + cfg->autorebase = AUTOREBASE_NEVER; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; diff --git a/environment.h b/environment.h index 17a3a628d2..46b2f0d861 100644 --- a/environment.h +++ b/environment.h @@ -102,6 +102,13 @@ enum push_default_type { PUSH_DEFAULT_UNSPECIFIED }; +enum rebase_setup_type { + AUTOREBASE_NEVER = 0, + AUTOREBASE_LOCAL, + AUTOREBASE_REMOTE, + AUTOREBASE_ALWAYS +}; + struct repo_config_values { /* section "core" config values */ char *attributes_file; @@ -112,6 +119,7 @@ struct repo_config_values { char *apply_default_whitespace; char *apply_default_ignorewhitespace; enum push_default_type push_default; + enum rebase_setup_type autorebase; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -205,14 +213,6 @@ extern unsigned long pack_size_limit_cfg; extern int protect_hfs; extern int protect_ntfs; -enum rebase_setup_type { - AUTOREBASE_NEVER = 0, - AUTOREBASE_LOCAL, - AUTOREBASE_REMOTE, - AUTOREBASE_ALWAYS -}; -extern enum rebase_setup_type autorebase; - enum object_creation_mode { OBJECT_CREATION_USES_HARDLINKS = 0, OBJECT_CREATION_USES_RENAMES = 1 From b5efc34b10b3484e18e7a55260f0f06e423eba28 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:24 +0800 Subject: [PATCH 09/10] environment: move object_creation_mode into repo_config_values The global variable 'object_creation_mode' controls how Git creates object files, specifically determining whether to use hardlinks or renames when moving temporary files into the object database. Move it into 'struct repo_config_values' to continue the libification effort. Move the 'enum object_creation_mode' definition higher up in 'environment.h' to ensure it is visible to the structure. Initialize the per-repository value to its default macro value OBJECT_CREATION_MODE inside 'repo_config_values_init()'. Update configuration parsing in 'git_default_core_config()' to write directly to the repository-specific configuration structure. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- environment.c | 6 +++--- environment.h | 12 ++++++------ object-file.c | 3 ++- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/environment.c b/environment.c index c0bf7577b7..ef3c032e0c 100644 --- a/environment.c +++ b/environment.c @@ -60,7 +60,6 @@ char *check_roundtrip_encoding; #ifndef OBJECT_CREATION_MODE #define OBJECT_CREATION_MODE OBJECT_CREATION_USES_HARDLINKS #endif -enum object_creation_mode object_creation_mode = OBJECT_CREATION_MODE; int grafts_keep_true_parents; unsigned long pack_size_limit_cfg; @@ -512,9 +511,9 @@ int git_default_core_config(const char *var, const char *value, if (!value) return config_error_nonbool(var); if (!strcmp(value, "rename")) - object_creation_mode = OBJECT_CREATION_USES_RENAMES; + cfg->object_creation_mode = OBJECT_CREATION_USES_RENAMES; else if (!strcmp(value, "link")) - object_creation_mode = OBJECT_CREATION_USES_HARDLINKS; + cfg->object_creation_mode = OBJECT_CREATION_USES_HARDLINKS; else die(_("invalid mode for object creation: %s"), value); return 0; @@ -728,6 +727,7 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->apply_default_ignorewhitespace = NULL; cfg->push_default = PUSH_DEFAULT_UNSPECIFIED; cfg->autorebase = AUTOREBASE_NEVER; + cfg->object_creation_mode = OBJECT_CREATION_MODE; cfg->apply_sparse_checkout = 0; cfg->branch_track = BRANCH_TRACK_REMOTE; cfg->trust_ctime = 1; diff --git a/environment.h b/environment.h index 46b2f0d861..a47a5c83db 100644 --- a/environment.h +++ b/environment.h @@ -109,6 +109,11 @@ enum rebase_setup_type { AUTOREBASE_ALWAYS }; +enum object_creation_mode { + OBJECT_CREATION_USES_HARDLINKS = 0, + OBJECT_CREATION_USES_RENAMES = 1 +}; + struct repo_config_values { /* section "core" config values */ char *attributes_file; @@ -120,6 +125,7 @@ struct repo_config_values { char *apply_default_ignorewhitespace; enum push_default_type push_default; enum rebase_setup_type autorebase; + enum object_creation_mode object_creation_mode; int apply_sparse_checkout; int trust_ctime; int check_stat; @@ -213,12 +219,6 @@ extern unsigned long pack_size_limit_cfg; extern int protect_hfs; extern int protect_ntfs; -enum object_creation_mode { - OBJECT_CREATION_USES_HARDLINKS = 0, - OBJECT_CREATION_USES_RENAMES = 1 -}; -extern enum object_creation_mode object_creation_mode; - extern int grafts_keep_true_parents; const char *get_log_output_encoding(void); diff --git a/object-file.c b/object-file.c index e3d92bbda2..2678090468 100644 --- a/object-file.c +++ b/object-file.c @@ -411,11 +411,12 @@ int finalize_object_file_flags(struct repository *repo, { unsigned retries = 0; int ret; + struct repo_config_values *cfg = repo_config_values(repo); retry: ret = 0; - if (object_creation_mode == OBJECT_CREATION_USES_RENAMES) + if (cfg->object_creation_mode == OBJECT_CREATION_USES_RENAMES) goto try_rename; else if (link(tmpfile, filename)) ret = errno; From 5eac7ac7151a27025386d48e21c4608115a16db6 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Tue, 14 Jul 2026 11:25:25 +0800 Subject: [PATCH 10/10] repository: adjust the comment of config_values_private_ The configurations in 'struct config_values_private_' are not all parsed in 'git_default_config()'. For example, 'pager_program' is now parsed in 'pager.c'. Therefore, update the comment. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- repository.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/repository.h b/repository.h index 36e2db2633..9093e6af93 100644 --- a/repository.h +++ b/repository.h @@ -152,7 +152,7 @@ struct repository { /* Repository's compatibility hash algorithm. */ const struct git_hash_algo *compat_hash_algo; - /* Repository's config values parsed by git_default_config() */ + /* Repository-specific configuration values. */ struct repo_config_values config_values_private_; /* Repository's reference storage format, as serialized on disk. */