{"thread":{"id":"65718","subject":"[PATCH v1 2/4] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","startedAt":"2026-05-30T16:05:39Z","lastAt":"2026-07-20T10:54:01Z","messageCount":62,"participants":["Tian Yuchen","Christian Couder","Junio C Hamano","Patrick Steinhardt","SZEDER Gábor"],"isPatch":true,"patchVersion":1,"patchTotal":4},"messages":[{"id":"544298","messageId":"20260530160520.77859-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260530160520.77859-1-cat@malon.dev","subject":"[PATCH v1 2/4] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-05-30T16:05:17Z","receivedAt":"2026-05-30T16:05:39Z","isPatch":true,"body":"The ce_mode_from_stat() function is declared as a static inline function\nin 'read-cache.h'. As we want to migrate configuration variables, this\nhelper function will need access to corresponding repository-specific\nconfiguration logic. Move the implementation to 'read-cache.c' to\ncleanly encapsulate its dependencies.\n\nNote that the 'extern int trust_executable_bit, has_symlinks;' line is\ndiscarded because it's not necessary when the function lives in\n\"read-cache.c\".\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 read-cache.c | 13 +++++++++++++\n read-cache.h | 16 ++--------------\n 2 files changed, 15 insertions(+), 14 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..54150fe756 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -202,6 +202,19 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \t}\n }\n \n+unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n+{\n+\tif (!has_symlinks && S_ISREG(mode) &&\n+\t    ce && S_ISLNK(ce->ce_mode))\n+\t\treturn ce->ce_mode;\n+\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\t\tif (ce && S_ISREG(ce->ce_mode))\n+\t\t\treturn ce->ce_mode;\n+\t\treturn create_ce_mode(0666);\n+\t}\n+\treturn create_ce_mode(mode);\n+}\n+\n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..3c4af2faeb 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -5,20 +5,8 @@\n #include \"object.h\"\n #include \"pathspec.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n-\t\t\t\t\t     unsigned int mode)\n-{\n-\textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n-\t    ce && S_ISLNK(ce->ce_mode))\n-\t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n-\t\tif (ce && S_ISREG(ce->ce_mode))\n-\t\t\treturn ce->ce_mode;\n-\t\treturn create_ce_mode(0666);\n-\t}\n-\treturn create_ce_mode(mode);\n-}\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+\t\t\t\tunsigned int mode);\n \n static inline int ce_to_dtype(const struct cache_entry *ce)\n {\n-- \n2.43.0\n\n"},{"id":"544299","messageId":"20260530160520.77859-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260530160520.77859-1-cat@malon.dev","subject":"[PATCH v1 3/4] environment: move 'trust_executable_bit' into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-05-30T16:05:18Z","receivedAt":"2026-05-30T16:05:43Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configuration into the\nrepository-specific 'repo_config_values' struct.\n\nFor now, associated functions in read-cache.c access this configuration\nby explicitly falling back to '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 apply.c       |  4 +++-\n environment.c |  4 ++--\n environment.h |  2 +-\n read-cache.c  | 15 +++++++++++----\n 4 files changed, 17 insertions(+), 8 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..73ca9907f8 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3890,10 +3890,12 @@ static int check_preimage(struct apply_state *state,\n \t}\n \n \tif (!state->cached && !previous) {\n+\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (cfg->trust_executable_bit || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..94f74f39e6 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -305,7 +304,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +719,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..72c400923d 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+\tint trust_executable_bit;\n \tint apply_sparse_checkout;\n \n \t/* section \"branch\" config values */\n@@ -160,7 +161,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex 54150fe756..18af533649 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,10 +204,12 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tif (!has_symlinks && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (!cfg->trust_executable_bit && S_ISREG(mode)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n@@ -217,11 +219,13 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (cfg->trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -321,6 +325,7 @@ static int ce_modified_check_fs(struct index_state *istate,\n static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n {\n \tunsigned int changed = 0;\n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n \n \tif (ce->ce_flags & CE_REMOVE)\n \t\treturn MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;\n@@ -331,7 +336,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (cfg->trust_executable_bit &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -732,6 +737,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\t\t  (intent_only ? ADD_CACHE_NEW_ONLY : 0));\n \tunsigned hash_flags = pretend ? 0 : INDEX_WRITE_OBJECT;\n \n+\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\n \tif (flags & ADD_CACHE_RENORMALIZE)\n \t\thash_flags |= INDEX_RENORMALIZE;\n \n@@ -752,7 +759,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (cfg->trust_executable_bit && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"544300","messageId":"20260530160520.77859-5-cat@malon.dev","threadId":"65718","inReplyTo":"20260530160520.77859-1-cat@malon.dev","subject":"[PATCH v1 4/4] read-cache: pass 'istate' to stat/mode helper functions","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-05-30T16:05:19Z","receivedAt":"2026-05-30T16:05:51Z","isPatch":true,"body":"In the previous commit, the gloabl 'trust_executable_bit' was\nmigrated into 'repo_config_values', but low-level helpers in\nread-cache.c still relied on 'the_repository' to access it.\n\nRefactor the signatures of ce_mode_from_stat(), st_mode_from_ce(),\nfake_lstat(), and check_removed() to accept a 'struct\nindex_state *istate'. This allows these functions to retrieve the\nrepository context via 'istate->repo'.\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                |  4 ++--\n builtin/update-index.c |  2 +-\n diff-lib.c             | 20 ++++++++++----------\n read-cache-ll.h        |  2 +-\n read-cache.c           | 31 +++++++++++++++++++------------\n read-cache.h           |  3 ++-\n 6 files changed, 35 insertions(+), 27 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 73ca9907f8..a81bb29a6f 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3890,13 +3890,13 @@ static int check_preimage(struct apply_state *state,\n \t}\n \n \tif (!state->cached && !previous) {\n-\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\t\tstruct repo_config_values *cfg = repo_config_values(state->repo);\n \n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n \t\tif (cfg->trust_executable_bit || !S_ISREG(st->st_mode))\n-\t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n+\t\t\tst_mode = ce_mode_from_stat(state->repo->index, *ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\n \t\telse\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 8a5907767b..3f6967bd84 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -293,7 +293,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len\n \tce->ce_flags = create_ce_flags(0);\n \tce->ce_namelen = len;\n \tfill_stat_cache_info(the_repository->index, ce, st);\n-\tce->ce_mode = ce_mode_from_stat(old, st->st_mode);\n+\tce->ce_mode = ce_mode_from_stat(the_repository->index, old, st->st_mode);\n \n \tif (index_path(the_repository->index, &ce->oid, path, st,\n \t\t       info_only ? 0 : INDEX_WRITE_OBJECT)) {\ndiff --git a/diff-lib.c b/diff-lib.c\nindex ae91027a02..95fd3ba4b9 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -39,14 +39,14 @@\n  * exists for ce that is a submodule -- it is a submodule that is not\n  * checked out).  Return negative for an error.\n  */\n-static int check_removed(const struct cache_entry *ce, struct stat *st)\n+static int check_removed(struct index_state *istate, const struct cache_entry *ce, struct stat *st)\n {\n \tint stat_err;\n \n \tif (!(ce->ce_flags & CE_FSMONITOR_VALID))\n \t\tstat_err = lstat(ce->name, st);\n \telse\n-\t\tstat_err = fake_lstat(ce, st);\n+\t\tstat_err = fake_lstat(istate, ce, st);\n \tif (stat_err < 0) {\n \t\tif (!is_missing_file_error(errno))\n \t\t\treturn -1;\n@@ -158,9 +158,9 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\tint num_compare_stages = 0;\n \t\t\tstruct stat st;\n \n-\t\t\tchanged = check_removed(ce, &st);\n+\t\t\tchanged = check_removed(revs->repo->index, ce, &st);\n \t\t\tif (!changed)\n-\t\t\t\twt_mode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\t\twt_mode = ce_mode_from_stat(revs->repo->index, ce, st.st_mode);\n \t\t\telse {\n \t\t\t\tif (changed < 0) {\n \t\t\t\t\tperror(ce->name);\n@@ -193,7 +193,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t\t\tnum_compare_stages++;\n \t\t\t\t\toidcpy(&dpath->parent[stage - 2].oid,\n \t\t\t\t\t       &nce->oid);\n-\t\t\t\t\tdpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);\n+\t\t\t\t\tdpath->parent[stage-2].mode = ce_mode_from_stat(revs->repo->index, nce, mode);\n \t\t\t\t\tdpath->parent[stage-2].status =\n \t\t\t\t\t\tDIFF_STATUS_MODIFIED;\n \t\t\t\t}\n@@ -249,7 +249,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t} else {\n \t\t\tstruct stat st;\n \n-\t\t\tchanged = check_removed(ce, &st);\n+\t\t\tchanged = check_removed(revs->repo->index, ce, &st);\n \t\t\tif (changed) {\n \t\t\t\tif (changed < 0) {\n \t\t\t\t\tperror(ce->name);\n@@ -262,7 +262,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t\tcontinue;\n \t\t\t} else if (revs->diffopt.ita_invisible_in_index &&\n \t\t\t\t   ce_intent_to_add(ce)) {\n-\t\t\t\tnewmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\t\tnewmode = ce_mode_from_stat(revs->repo->index, ce, st.st_mode);\n \t\t\t\tdiff_addremove(&revs->diffopt, '+', newmode,\n \t\t\t\t\t       null_oid(the_hash_algo), 0, ce->name, 0);\n \t\t\t\tcontinue;\n@@ -270,7 +270,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \n \t\t\tchanged = match_stat_with_submodule(&revs->diffopt, ce, &st,\n \t\t\t\t\t\t\t    ce_option, &dirty_submodule);\n-\t\t\tnewmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\tnewmode = ce_mode_from_stat(revs->repo->index, ce, st.st_mode);\n \t\t}\n \n \t\tif (!changed && !dirty_submodule) {\n@@ -324,7 +324,7 @@ static int get_stat_data(const struct cache_entry *ce,\n \tif (!cached && !ce_uptodate(ce)) {\n \t\tint changed;\n \t\tstruct stat st;\n-\t\tchanged = check_removed(ce, &st);\n+\t\tchanged = check_removed(diffopt->repo->index, ce, &st);\n \t\tif (changed < 0)\n \t\t\treturn -1;\n \t\telse if (changed) {\n@@ -338,7 +338,7 @@ static int get_stat_data(const struct cache_entry *ce,\n \t\tchanged = match_stat_with_submodule(diffopt, ce, &st,\n \t\t\t\t\t\t    0, dirty_submodule);\n \t\tif (changed) {\n-\t\t\tmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\tmode = ce_mode_from_stat(diffopt->repo->index, ce, st.st_mode);\n \t\t\toid = null_oid(the_hash_algo);\n \t\t}\n \t}\ndiff --git a/read-cache-ll.h b/read-cache-ll.h\nindex 2c8b4b21b1..9fb9bedfbf 100644\n--- a/read-cache-ll.h\n+++ b/read-cache-ll.h\n@@ -442,7 +442,7 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n  * for lstat() for a tracked path that is known to be up-to-date via\n  * some out-of-line means (like fsmonitor).\n  */\n-int fake_lstat(const struct cache_entry *ce, struct stat *st);\n+int fake_lstat(struct index_state *istate, const struct cache_entry *ce, struct stat *st);\n \n #define REFRESH_REALLY                   (1 << 0) /* ignore_valid */\n #define REFRESH_UNMERGED                 (1 << 1) /* allow unmerged */\ndiff --git a/read-cache.c b/read-cache.c\nindex 18af533649..28e7f24382 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -202,9 +202,12 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \t}\n }\n \n-unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n+unsigned int ce_mode_from_stat(struct index_state *istate,\n+\t\t\t       const struct cache_entry *ce,\n+\t\t\t       unsigned int mode)\n {\n-\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\tstruct repository *repo = (istate && istate->repo) ? istate->repo : the_repository;\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n \n \tif (!has_symlinks && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n@@ -217,9 +220,10 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n \treturn create_ce_mode(mode);\n }\n \n-static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n+static unsigned int st_mode_from_ce(struct index_state *istate, const struct cache_entry *ce)\n {\n-\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\tstruct repository *repo = (istate && istate->repo) ? istate->repo : the_repository;\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n \n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n@@ -235,10 +239,10 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \t}\n }\n \n-int fake_lstat(const struct cache_entry *ce, struct stat *st)\n+int fake_lstat(struct index_state *istate, const struct cache_entry *ce, struct stat *st)\n {\n \tfake_lstat_data(&ce->ce_stat_data, st);\n-\tst->st_mode = st_mode_from_ce(ce);\n+\tst->st_mode = st_mode_from_ce(istate, ce);\n \n \t/* always succeed as lstat() replacement */\n \treturn 0;\n@@ -322,10 +326,12 @@ static int ce_modified_check_fs(struct index_state *istate,\n \treturn 0;\n }\n \n-static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n+static int ce_match_stat_basic(struct index_state *istate,\n+\t\t\t       const struct cache_entry *ce, struct stat *st)\n {\n \tunsigned int changed = 0;\n-\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\tstruct repository *repo = (istate && istate->repo) ? istate->repo : the_repository;\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n \n \tif (ce->ce_flags & CE_REMOVE)\n \t\treturn MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;\n@@ -430,7 +436,7 @@ int ie_match_stat(struct index_state *istate,\n \tif (ce_intent_to_add(ce))\n \t\treturn DATA_CHANGED | TYPE_CHANGED | MODE_CHANGED;\n \n-\tchanged = ce_match_stat_basic(ce, st);\n+\tchanged = ce_match_stat_basic(istate, ce, st);\n \n \t/*\n \t * Within 1 second of this sequence:\n@@ -737,7 +743,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\t\t  (intent_only ? ADD_CACHE_NEW_ONLY : 0));\n \tunsigned hash_flags = pretend ? 0 : INDEX_WRITE_OBJECT;\n \n-\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\tstruct repository *repo = (istate && istate->repo) ? istate->repo : the_repository;\n+\tstruct repo_config_values *cfg = repo_config_values(repo);\n \n \tif (flags & ADD_CACHE_RENORMALIZE)\n \t\thash_flags |= INDEX_RENORMALIZE;\n@@ -769,7 +776,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tint pos = index_name_pos_also_unmerged(istate, path, namelen);\n \n \t\tent = (0 <= pos) ? istate->cache[pos] : NULL;\n-\t\tce->ce_mode = ce_mode_from_stat(ent, st_mode);\n+\t\tce->ce_mode = ce_mode_from_stat(istate, ent, st_mode);\n \t}\n \n \t/* When core.ignorecase=true, determine if a directory of the same name but differing\n@@ -2592,7 +2599,7 @@ static void ce_smudge_racily_clean_entry(struct index_state *istate,\n \n \tif (lstat(ce->name, &st) < 0)\n \t\treturn;\n-\tif (ce_match_stat_basic(ce, &st))\n+\tif (ce_match_stat_basic(istate, ce, &st))\n \t\treturn;\n \tif (ce_modified_check_fs(istate, ce, &st)) {\n \t\t/* This is \"racily clean\"; smudge it.  Note that this\ndiff --git a/read-cache.h b/read-cache.h\nindex 3c4af2faeb..61299ed95b 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -5,7 +5,8 @@\n #include \"object.h\"\n #include \"pathspec.h\"\n \n-unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+unsigned int ce_mode_from_stat(struct index_state *istate,\n+\t\t\t\tconst struct cache_entry *ce,\n \t\t\t\tunsigned int mode);\n \n static inline int ce_to_dtype(const struct cache_entry *ce)\n-- \n2.43.0\n\n"},{"id":"544301","messageId":"20260530160520.77859-1-cat@malon.dev","threadId":"65718","inReplyTo":null,"subject":"[PATCH v1 0/4] environment.c: migrate 'trust_executable_bit' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-05-30T16:05:15Z","receivedAt":"2026-05-30T16:06:00Z","isPatch":true,"body":"The 'core.filemode' configuration, which is stored as a global\nvariable 'trust_executable_bit', is a core filesystem capability flag.\n\nMove it into 'repo_config_values' to tie it to the specific\nrepository instance it was read from. Eager parsing is maintained\nbecause this flag is heavily consulted in hot paths.\n\nTo avoid falling back to 'the_repository', refactor the signatures\nof helper functions:\n\n - ce_mode_from_stat()\n - st_mode_from_ce()\n - fake_lstat()\n - check_removed()\n\nThese functions now accept a 'struct index_state *istate', ensuring\nthe correct context is seamlessly passed down to the lowest levels.\n\nNote: 'repo_config_values()' still does not support any 'struct\nrepository' other than 'the_repository'. In other words, this series\nof patches is laying the groundwork for the eventual elimination of\n'the_repository'.\n\nPrevious related work:\n\n - [PATCH 2/6] config: add trust_executable_bit to global config\n\n - [PATCH] Refactor 'trust_executable_bit' to repository-scoped setting \n (This series of patches was unsuccessful because the target location selected\n was 'struct repo_settings', which our analysis indicated was not the\n optimal choice. For further details, please see: [1])\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\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 (4):\n  read-cache: remove redundant extern declarations\n  read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'\n  environment: move 'trust_executable_bit' into repo_config_values\n  read-cache: pass 'istate' to stat/mode helper functions\n\n apply.c                |  6 ++++--\n builtin/update-index.c |  2 +-\n diff-lib.c             | 20 +++++++++---------\n environment.c          |  4 ++--\n environment.h          |  2 +-\n read-cache-ll.h        |  2 +-\n read-cache.c           | 47 ++++++++++++++++++++++++++++++++----------\n read-cache.h           | 17 +++------------\n 8 files changed, 58 insertions(+), 42 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"544302","messageId":"20260530160520.77859-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260530160520.77859-1-cat@malon.dev","subject":"[PATCH v1 1/4] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-05-30T16:05:16Z","receivedAt":"2026-05-30T16:06:00Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"544303","messageId":"CAP8UFD1GJ=caPh-M97KLCfB1ZKtpomzosYN0uYBOnay+G23GcA@mail.gmail.com","threadId":"65718","inReplyTo":"20260530160520.77859-4-cat@malon.dev","subject":"Re: [PATCH v1 3/4] environment: move 'trust_executable_bit' into repo_config_values","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2026-05-30T18:02:08Z","receivedAt":"2026-05-30T18:02:23Z","isPatch":true,"body":"On Sat, May 30, 2026 at 6:05 PM Tian Yuchen <cat@malon.dev> wrote:\n\n> @@ -720,5 +719,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>  {\n>         cfg->attributes_file = NULL;\n>         cfg->apply_sparse_checkout = 0;\n> +       cfg->trust_executable_bit = 1;\n\nHere `trust_executable_bit` is processed after `apply_sparse_checkout` ...\n\n>         cfg->branch_track = BRANCH_TRACK_REMOTE;\n>  }\n> diff --git a/environment.h b/environment.h\n> index 123a71cdc8..72c400923d 100644\n> --- a/environment.h\n> +++ b/environment.h\n> @@ -90,6 +90,7 @@ struct repository;\n>  struct repo_config_values {\n>         /* section \"core\" config values */\n>         char *attributes_file;\n> +       int trust_executable_bit;\n>         int apply_sparse_checkout;\n\n... but here it is before `apply_sparse_checkout`.\n\nI think it would make more sense to put `trust_executable_bit` after\n`apply_sparse_checkout` here.\n\n>         /* section \"branch\" config values */\n"},{"id":"544304","messageId":"CAP8UFD20yij=1ZEYnR74DoCJ3g=b39yOsUxZecYuuf7nFGaKyA@mail.gmail.com","threadId":"65718","inReplyTo":"20260530160520.77859-5-cat@malon.dev","subject":"Re: [PATCH v1 4/4] read-cache: pass 'istate' to stat/mode helper functions","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2026-05-30T18:14:01Z","receivedAt":"2026-05-30T18:14:13Z","isPatch":true,"body":"On Sat, May 30, 2026 at 6:05 PM Tian Yuchen <cat@malon.dev> wrote:\n>\n> In the previous commit, the gloabl 'trust_executable_bit' was\n\ns/gloabl/global/\n\n> migrated into 'repo_config_values', but low-level helpers in\n> read-cache.c still relied on 'the_repository' to access it.\n>\n> Refactor the signatures of ce_mode_from_stat(), st_mode_from_ce(),\n> fake_lstat(), and check_removed() to accept a 'struct\n> index_state *istate'. This allows these functions to retrieve the\n> repository context via 'istate->repo'.\n\nThe cover letter contains:\n\n\"In other words, this series of patches is laying the groundwork for\nthe eventual elimination of 'the_repository'.\"\n\nbut I think it would be also interesting to have something similar\nhere, as this is especially relevant to this commit.\n\nFor example maybe add something like \"which will help with removing\n'the_repository' in the future\" to the last sentence?\n"},{"id":"544311","messageId":"xmqq7bokebct.fsf@gitster.g","threadId":"65718","inReplyTo":"20260530160520.77859-4-cat@malon.dev","subject":"Re: [PATCH v1 3/4] environment: move 'trust_executable_bit' into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-05-30T23:17:22Z","receivedAt":"2026-05-30T23:17:25Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> diff --git a/apply.c b/apply.c\n> index 249248d4f2..73ca9907f8 100644\n> --- a/apply.c\n> +++ b/apply.c\n> @@ -3890,10 +3890,12 @@ static int check_preimage(struct apply_state *state,\n>  \t}\n>  \n>  \tif (!state->cached && !previous) {\n> +\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n> +\n>  \t\tif (*ce && !(*ce)->ce_mode)\n>  \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n>  \n> -\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n> +\t\tif (cfg->trust_executable_bit || !S_ISREG(st->st_mode))\n>  \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n>  \t\telse if (*ce)\n>  \t\t\tst_mode = (*ce)->ce_mode;\n> diff --git a/read-cache.c b/read-cache.c\n> index 54150fe756..18af533649 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -204,10 +204,12 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n>  \n>  unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n>  {\n> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n> +\n>  \tif (!has_symlinks && S_ISREG(mode) &&\n>  \t    ce && S_ISLNK(ce->ce_mode))\n>  \t\treturn ce->ce_mode;\n> -\tif (!trust_executable_bit && S_ISREG(mode)) {\n> +\tif (!cfg->trust_executable_bit && S_ISREG(mode)) {\n>  \t\tif (ce && S_ISREG(ce->ce_mode))\n>  \t\t\treturn ce->ce_mode;\n>  \t\treturn create_ce_mode(0666);\n\nHow hot are the code paths that call into this helper function?  In\nthe original under some condition, it was possible to return without\neven consulting the trust_executable_bit variable, but in the\nupdated code, the helper unconditionally makes a call to the\nrepo_config_values() helper function even before it knows it needs\nto know the value of trust_executable_bit.\n\n> @@ -217,11 +219,13 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n>  \n>  static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n>  {\n> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n> +\n>  \tswitch (ce->ce_mode & S_IFMT) {\n>  \tcase S_IFLNK:\n>  \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n>  \tcase S_IFREG:\n> -\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n> +\t\treturn (ce->ce_mode & (cfg->trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n>  \tcase S_IFGITLINK:\n>  \t\treturn S_IFDIR | 0755;\n>  \tcase S_IFDIR:\n\nDitto.\n\n> @@ -321,6 +325,7 @@ static int ce_modified_check_fs(struct index_state *istate,\n>  static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n>  {\n>  \tunsigned int changed = 0;\n> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n>  \n>  \tif (ce->ce_flags & CE_REMOVE)\n>  \t\treturn MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;\n> @@ -331,7 +336,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n>  \t\t/* We consider only the owner x bit to be relevant for\n>  \t\t * \"mode changes\"\n>  \t\t */\n> -\t\tif (trust_executable_bit &&\n> +\t\tif (cfg->trust_executable_bit &&\n>  \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n>  \t\t\tchanged |= MODE_CHANGED;\n>  \t\tbreak;\n\nDitto.\n\n> @@ -732,6 +737,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n>  \t\t\t  (intent_only ? ADD_CACHE_NEW_ONLY : 0));\n>  \tunsigned hash_flags = pretend ? 0 : INDEX_WRITE_OBJECT;\n>  \n> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n> +\n\nLose the excess blank line before the new declaration.\n\n>  \tif (flags & ADD_CACHE_RENORMALIZE)\n>  \t\thash_flags |= INDEX_RENORMALIZE;\n>  \n> @@ -752,7 +759,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n>  \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n>  \n>  \n> -\tif (trust_executable_bit && has_symlinks) {\n> +\tif (cfg->trust_executable_bit && has_symlinks) {\n>  \t\tce->ce_mode = create_ce_mode(st_mode);\n>  \t} else {\n>  \t\t/* If there is an existing entry, pick the mode bits and type\n\nAlmost all of these places that care about trust_executable_bit also\ncares about has_symlinks.  I wonder if they should be converted to\nrepo-local settings in the same series.\n"},{"id":"544375","messageId":"e0d5b1af-b040-49e2-90f9-d8325682826b@malon.dev","threadId":"65718","inReplyTo":"xmqq7bokebct.fsf@gitster.g","subject":"Re: [PATCH v1 3/4] environment: move 'trust_executable_bit' into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-01T10:10:08Z","receivedAt":"2026-06-01T10:10:23Z","isPatch":true,"body":"Hi Junio,\n\nThanks for the feedback!\n\nOn 5/31/26 07:17, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> diff --git a/apply.c b/apply.c\n>> index 249248d4f2..73ca9907f8 100644\n>> --- a/apply.c\n>> +++ b/apply.c\n>> @@ -3890,10 +3890,12 @@ static int check_preimage(struct apply_state *state,\n>>   \t}\n>>   \n>>   \tif (!state->cached && !previous) {\n>> +\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n>> +\n>>   \t\tif (*ce && !(*ce)->ce_mode)\n>>   \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n>>   \n>> -\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n>> +\t\tif (cfg->trust_executable_bit || !S_ISREG(st->st_mode))\n>>   \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n>>   \t\telse if (*ce)\n>>   \t\t\tst_mode = (*ce)->ce_mode;\n>> diff --git a/read-cache.c b/read-cache.c\n>> index 54150fe756..18af533649 100644\n>> --- a/read-cache.c\n>> +++ b/read-cache.c\n>> @@ -204,10 +204,12 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n>>   \n>>   unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n>>   {\n>> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n>> +\n>>   \tif (!has_symlinks && S_ISREG(mode) &&\n>>   \t    ce && S_ISLNK(ce->ce_mode))\n>>   \t\treturn ce->ce_mode;\n>> -\tif (!trust_executable_bit && S_ISREG(mode)) {\n>> +\tif (!cfg->trust_executable_bit && S_ISREG(mode)) {\n>>   \t\tif (ce && S_ISREG(ce->ce_mode))\n>>   \t\t\treturn ce->ce_mode;\n>>   \t\treturn create_ce_mode(0666);\n> \n> How hot are the code paths that call into this helper function?  In\n> the original under some condition, it was possible to return without\n> even consulting the trust_executable_bit variable, but in the\n> updated code, the helper unconditionally makes a call to the\n> repo_config_values() helper function even before it knows it needs\n> to know the value of trust_executable_bit.\n\nThat sounds reasonable to me. I’ll adjust the conditional logic in some \nof the statements so that they short-circuit appropriately to avoid \nperformance overhead.\n\n> \n>> @@ -217,11 +219,13 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n>>   \n>>   static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n>>   {\n>> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n>> +\n>>   \tswitch (ce->ce_mode & S_IFMT) {\n>>   \tcase S_IFLNK:\n>>   \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n>>   \tcase S_IFREG:\n>> -\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n>> +\t\treturn (ce->ce_mode & (cfg->trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n>>   \tcase S_IFGITLINK:\n>>   \t\treturn S_IFDIR | 0755;\n>>   \tcase S_IFDIR:\n> \n> Ditto.\n> \n>> @@ -321,6 +325,7 @@ static int ce_modified_check_fs(struct index_state *istate,\n>>   static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n>>   {\n>>   \tunsigned int changed = 0;\n>> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n>>   \n>>   \tif (ce->ce_flags & CE_REMOVE)\n>>   \t\treturn MODE_CHANGED | DATA_CHANGED | TYPE_CHANGED;\n>> @@ -331,7 +336,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n>>   \t\t/* We consider only the owner x bit to be relevant for\n>>   \t\t * \"mode changes\"\n>>   \t\t */\n>> -\t\tif (trust_executable_bit &&\n>> +\t\tif (cfg->trust_executable_bit &&\n>>   \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n>>   \t\t\tchanged |= MODE_CHANGED;\n>>   \t\tbreak;\n> \n> Ditto.\n> \n>> @@ -732,6 +737,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n>>   \t\t\t  (intent_only ? ADD_CACHE_NEW_ONLY : 0));\n>>   \tunsigned hash_flags = pretend ? 0 : INDEX_WRITE_OBJECT;\n>>   \n>> +\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n>> +\n> \n> Lose the excess blank line before the new declaration.\n> \n>>   \tif (flags & ADD_CACHE_RENORMALIZE)\n>>   \t\thash_flags |= INDEX_RENORMALIZE;\n>>   \n>> @@ -752,7 +759,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n>>   \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n>>   \n>>   \n>> -\tif (trust_executable_bit && has_symlinks) {\n>> +\tif (cfg->trust_executable_bit && has_symlinks) {\n>>   \t\tce->ce_mode = create_ce_mode(st_mode);\n>>   \t} else {\n>>   \t\t/* If there is an existing entry, pick the mode bits and type\n> \n> Almost all of these places that care about trust_executable_bit also\n> cares about has_symlinks.  I wonder if they should be converted to\n> repo-local settings in the same series.\n\nThat’s true: I had actually planned to start migrating has_symlinks as \nsoon as this series was approved. Since you think it would be better to \nmerge them into a single series, I’ll go ahead and do that ;)\n\nThanks, yuchen\n\n"},{"id":"544416","messageId":"8083b217-4a56-48ee-b34d-b4596d45e382@malon.dev","threadId":"65718","inReplyTo":"e0d5b1af-b040-49e2-90f9-d8325682826b@malon.dev","subject":"Re: [PATCH v1 3/4] environment: move 'trust_executable_bit' into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-01T18:03:15Z","receivedAt":"2026-06-01T18:03:24Z","isPatch":true,"body":"On 6/1/26 18:10, Tian Yuchen wrote:\n\n> That’s true: I had actually planned to start migrating has_symlinks as \n> soon as this series was approved. Since you think it would be better to \n> merge them into a single series, I’ll go ahead and do that ;)\n> \n\nI’ve found that migrating has_symlinks seems to be quite a tricky \nbusiness. Some callers in certain files pass very few parameters, and \nthe call stack is quite deep, if I am correct. so I feel that adding a \nrepo for this purpose might be overkill. Perhaps it would be better to \nfocus on trust_executable_bit for now?\n\nRegards, yuchen\n"},{"id":"544670","messageId":"aiEfkOtlnemkWgXV@pks.im","threadId":"65718","inReplyTo":"20260530160520.77859-3-cat@malon.dev","subject":"Re: [PATCH v1 2/4] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-06-04T06:47:44Z","receivedAt":"2026-06-04T06:47:51Z","isPatch":true,"body":"On Sun, May 31, 2026 at 12:05:17AM +0800, Tian Yuchen wrote:\n> diff --git a/read-cache.h b/read-cache.h\n> index 043da1f1aa..3c4af2faeb 100644\n> --- a/read-cache.h\n> +++ b/read-cache.h\n> @@ -5,20 +5,8 @@\n>  #include \"object.h\"\n>  #include \"pathspec.h\"\n>  \n> -static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n> -\t\t\t\t\t     unsigned int mode)\n> -{\n> -\textern int trust_executable_bit, has_symlinks;\n> -\tif (!has_symlinks && S_ISREG(mode) &&\n> -\t    ce && S_ISLNK(ce->ce_mode))\n> -\t\treturn ce->ce_mode;\n> -\tif (!trust_executable_bit && S_ISREG(mode)) {\n> -\t\tif (ce && S_ISREG(ce->ce_mode))\n> -\t\t\treturn ce->ce_mode;\n> -\t\treturn create_ce_mode(0666);\n> -\t}\n> -\treturn create_ce_mode(mode);\n> -}\n> +unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n> +\t\t\t\tunsigned int mode);\n\nThis is moving goalposts a bit, so please feel free to ignore: should we\nmaybe add a small comment what the function does while at it?\n\nPatrick\n"},{"id":"545075","messageId":"xmqqcxxzsu7p.fsf@gitster.g","threadId":"65718","inReplyTo":"8083b217-4a56-48ee-b34d-b4596d45e382@malon.dev","subject":"Re: [PATCH v1 3/4] environment: move 'trust_executable_bit' into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-09T13:45:46Z","receivedAt":"2026-06-09T13:45:49Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> On 6/1/26 18:10, Tian Yuchen wrote:\n>\n>> That’s true: I had actually planned to start migrating has_symlinks as \n>> soon as this series was approved. Since you think it would be better to \n>> merge them into a single series, I’ll go ahead and do that ;)\n>> \n>\n> I’ve found that migrating has_symlinks seems to be quite a tricky \n> business. Some callers in certain files pass very few parameters, and \n> the call stack is quite deep, if I am correct. so I feel that adding a \n> repo for this purpose might be overkill. Perhaps it would be better to \n> focus on trust_executable_bit for now?\n>\n> Regards, yuchen\n\nSounds fine.  Thanks for digging.\n"},{"id":"545109","messageId":"e5d7f762-1e3a-4e1b-b54a-e2f757b1d878@malon.dev","threadId":"65718","inReplyTo":"aiEfkOtlnemkWgXV@pks.im","subject":"Re: [PATCH v1 2/4] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-10T08:30:37Z","receivedAt":"2026-06-10T08:30:50Z","isPatch":true,"body":"On 6/4/26 14:47, Patrick Steinhardt wrote:\n> This is moving goalposts a bit, so please feel free to ignore: should we\n> maybe add a small comment what the function does while at it?\n\nYes indeed, the original commit message looks a bit bare, I’ll add to it \nin the next reroll. Thanks!\n\nRegards, yuchen\n"},{"id":"545123","messageId":"20260610093635.139719-1-cat@malon.dev","threadId":"65718","inReplyTo":"20260530160520.77859-1-cat@malon.dev","subject":"[PATCH v2 0/3] environment: migrate 'trust_executable_bit' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-10T09:36:31Z","receivedAt":"2026-06-10T09:36:52Z","isPatch":true,"body":"The 'core.filemode' (stored as 'trust_executable_bit') configuration\nact as a core filesystem capability flag.\n\nThis series moves it into 'struct repo_config_values' to tie it to\nthe specific repository instance it was read from. Eager parsing\nis maintained because this flag is heavily consulted in hot paths.\n\nNote: 'repo_config_values()' still does not support any struct\nrepository other than the_repository due to how deeply these flags\nare accessed. In other words, this series of patches is laying\nthe groundwork for the eventual elimination of the_repository.\n\nPrevious related work:\n[PATCH 2/6] config: add trust_executable_bit to global config [1]\n[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]\n(This previous attempt was unsuccessful because the target location\nselected was 'struct repo_settings', which our analysis indicated\nwas not the optimal choice. For further details, please see: [3])\n\nChanges since V1:\n\nDropped istate plumbing: Dropped the last commit (\"read-cache: pass\n'istate' to stat/mode helper functions\") to keep the patch focused.\n\nIntroduced safe getter: Getter function 'repo_trust_executable_bit()'\nhas been introduced to retrieve configuration values safely.\n\nAdded comments for 'ce_mode_from_stat()'.\n\nThanks!\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\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 (3):\n  read-cache: remove redundant extern declarations\n  read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'\n  environment: move trust_executable_bit into repo_config_values\n\n apply.c       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  9 ++++++++-\n read-cache.c  | 28 +++++++++++++++++++++++-----\n read-cache.h  | 16 ++--------------\n 5 files changed, 43 insertions(+), 23 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"545124","messageId":"20260610093635.139719-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260610093635.139719-1-cat@malon.dev","subject":"[PATCH v2 1/3] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-10T09:36:32Z","receivedAt":"2026-06-10T09:36:57Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"545125","messageId":"20260610093635.139719-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260610093635.139719-1-cat@malon.dev","subject":"[PATCH v2 2/3] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-10T09:36:33Z","receivedAt":"2026-06-10T09:36:58Z","isPatch":true,"body":"The ce_mode_from_stat() function is declared as a static inline function\nin 'read-cache.h'. As we want to migrate configuration variables, this\nhelper function will need access to corresponding repository-specific\nconfiguration logic. Move the implementation to 'read-cache.c' to\ncleanly encapsulate its dependencies.\n\nNote that the 'extern int trust_executable_bit, has_symlinks;' line is\ndiscarded because it's not necessary when the function lives in\n\"read-cache.c\".\n\nAt present, this change has no visible impact, but it is crucial\nfor our future plans to pass in the repo context. Comment\nhas been added whilst 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 read-cache.c | 20 ++++++++++++++++++++\n read-cache.h | 16 ++--------------\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..cb4f4878c8 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -202,6 +202,26 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \t}\n }\n \n+/*\n+ * Determine the appropriate index mode for a file based on its stat()\n+ * information and the existing cache entry (if any).\n+ *\n+ * This function handles degradation for filesystems that lack\n+ * symlink support or reliable executable bits.\n+ */\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n+{\n+\tif (!has_symlinks && S_ISREG(mode) &&\n+\t    ce && S_ISLNK(ce->ce_mode))\n+\t\treturn ce->ce_mode;\n+\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\t\tif (ce && S_ISREG(ce->ce_mode))\n+\t\t\treturn ce->ce_mode;\n+\t\treturn create_ce_mode(0666);\n+\t}\n+\treturn create_ce_mode(mode);\n+}\n+\n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..3c4af2faeb 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -5,20 +5,8 @@\n #include \"object.h\"\n #include \"pathspec.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n-\t\t\t\t\t     unsigned int mode)\n-{\n-\textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n-\t    ce && S_ISLNK(ce->ce_mode))\n-\t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n-\t\tif (ce && S_ISREG(ce->ce_mode))\n-\t\t\treturn ce->ce_mode;\n-\t\treturn create_ce_mode(0666);\n-\t}\n-\treturn create_ce_mode(mode);\n-}\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+\t\t\t\tunsigned int mode);\n \n static inline int ce_to_dtype(const struct cache_entry *ce)\n {\n-- \n2.43.0\n\n"},{"id":"545126","messageId":"20260610093635.139719-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260610093635.139719-1-cat@malon.dev","subject":"[PATCH v2 3/3] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-10T09:36:34Z","receivedAt":"2026-06-10T09:37:03Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configurations\ninto the repository-specific 'repo_config_values'\nstruct. To ensure code readability, the getter functions\n'repo_trust_executable_bit()' has been introduced.\n\nFor now, associated functions access this configuration by\nexplicitly falling back to '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 apply.c       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  9 ++++++++-\n read-cache.c  |  8 ++++----\n 4 files changed, 22 insertions(+), 8 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..fbb907d3c0 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (repo_trust_executable_bit(the_repository) || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..75069a884d 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -142,6 +141,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+int repo_trust_executable_bit(struct repository *repo)\n+{\n+\treturn repo->gitdir?\n+\t\trepo_config_values(repo)->trust_executable_bit :\n+\t\t1;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..44b97be654 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 \tint apply_sparse_checkout;\n+\tint trust_executable_bit;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -123,6 +124,13 @@ 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+ * Getters for the `repo_trust_executable_bit` fields of `struct repo_config_values`.\n+ * They check `repo->gitdir` to prevent calling repo_config_values()\n+ * before the configuration is loaded or in bare environments.\n+ */\n+int repo_trust_executable_bit(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -160,7 +168,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex cb4f4878c8..89f5c88c58 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -214,7 +214,7 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n \tif (!has_symlinks && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n@@ -228,7 +228,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -338,7 +338,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (repo_trust_executable_bit(the_repository) &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -759,7 +759,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (repo_trust_executable_bit(the_repository) && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"545222","messageId":"21f74852-2209-4d77-94f4-b2b9412eb8e0@malon.dev","threadId":"65718","inReplyTo":"20260610093635.139719-4-cat@malon.dev","subject":"Re: [PATCH v2 3/3] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-11T02:58:54Z","receivedAt":"2026-06-11T02:59:02Z","isPatch":true,"body":"On 6/10/26 17:36, Tian Yuchen wrote:\n> Move the global 'trust_executable_bit' configurations\n> into the repository-specific 'repo_config_values'\n> struct. To ensure code readability, the getter functions\n> 'repo_trust_executable_bit()' has been introduced.\n> \n> For now, associated functions access this configuration by\n> explicitly falling back to 'the_repository'.\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       |  2 +-\n>   environment.c | 11 +++++++++--\n>   environment.h |  9 ++++++++-\n>   read-cache.c  |  8 ++++----\n>   4 files changed, 22 insertions(+), 8 deletions(-)\n> \n> diff --git a/apply.c b/apply.c\n> index 249248d4f2..fbb907d3c0 100644\n> --- a/apply.c\n> +++ b/apply.c\n> @@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n>   \t\tif (*ce && !(*ce)->ce_mode)\n>   \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n>   \n> -\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n> +\t\tif (repo_trust_executable_bit(the_repository) || !S_ISREG(st->st_mode))\n>   \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n>   \t\telse if (*ce)\n>   \t\t\tst_mode = (*ce)->ce_mode;\n> diff --git a/environment.c b/environment.c\n> index fc3ed8bb1c..75069a884d 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -41,7 +41,6 @@\n>   static int pack_compression_seen;\n>   static int zlib_compression_seen;\n>   \n> -int trust_executable_bit = 1;\n>   int trust_ctime = 1;\n>   int check_stat = 1;\n>   int has_symlinks = 1;\n> @@ -142,6 +141,13 @@ int is_bare_repository(void)\n>   \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>   }\n>   \n> +int repo_trust_executable_bit(struct repository *repo)\n> +{\n> +\treturn repo->gitdir?\n> +\t\trepo_config_values(repo)->trust_executable_bit :\n> +\t\t1;\n> +}\n> +\n>   int have_git_dir(void)\n>   {\n>   \treturn startup_info->have_repository\n> @@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n>   \n>   \t/* This needs a better name */\n>   \tif (!strcmp(var, \"core.filemode\")) {\n> -\t\ttrust_executable_bit = git_config_bool(var, value);\n> +\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n>   \t\treturn 0;\n>   \t}\n>   \tif (!strcmp(var, \"core.trustctime\")) {\n> @@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n>   {\n>   \tcfg->attributes_file = NULL;\n>   \tcfg->apply_sparse_checkout = 0;\n> +\tcfg->trust_executable_bit = 1;\n>   \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n>   }\n> diff --git a/environment.h b/environment.h\n> index 123a71cdc8..44b97be654 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>   \tint apply_sparse_checkout;\n> +\tint trust_executable_bit;\n>   \n>   \t/* section \"branch\" config values */\n>   \tenum branch_track branch_track;\n> @@ -123,6 +124,13 @@ 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> + * Getters for the `repo_trust_executable_bit` fields of `struct repo_config_values`.\n> + * They check `repo->gitdir` to prevent calling repo_config_values()\n> + * before the configuration is loaded or in bare environments.\n> + */\n> +int repo_trust_executable_bit(struct repository *repo);\n> +\n>   void repo_config_values_init(struct repo_config_values *cfg);\n>   \n>   /*\n> @@ -160,7 +168,6 @@ int is_bare_repository(void);\n>   extern char *git_work_tree_cfg;\n>   \n>   /* Environment bits from configuration mechanism */\n> -extern int trust_executable_bit;\n>   extern int trust_ctime;\n>   extern int check_stat;\n>   extern int has_symlinks;\n> diff --git a/read-cache.c b/read-cache.c\n> index cb4f4878c8..89f5c88c58 100644\n> --- a/read-cache.c\n> +++ b/read-cache.c\n> @@ -214,7 +214,7 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n>   \tif (!has_symlinks && S_ISREG(mode) &&\n>   \t    ce && S_ISLNK(ce->ce_mode))\n>   \t\treturn ce->ce_mode;\n> -\tif (!trust_executable_bit && S_ISREG(mode)) {\n> +\tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n>   \t\tif (ce && S_ISREG(ce->ce_mode))\n>   \t\t\treturn ce->ce_mode;\n>   \t\treturn create_ce_mode(0666);\n> @@ -228,7 +228,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n>   \tcase S_IFLNK:\n>   \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n>   \tcase S_IFREG:\n> -\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n> +\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n>   \tcase S_IFGITLINK:\n>   \t\treturn S_IFDIR | 0755;\n>   \tcase S_IFDIR:\n> @@ -338,7 +338,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n>   \t\t/* We consider only the owner x bit to be relevant for\n>   \t\t * \"mode changes\"\n>   \t\t */\n> -\t\tif (trust_executable_bit &&\n> +\t\tif (repo_trust_executable_bit(the_repository) &&\n>   \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n>   \t\t\tchanged |= MODE_CHANGED;\n>   \t\tbreak;\n> @@ -759,7 +759,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n>   \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n>   \n>   \n> -\tif (trust_executable_bit && has_symlinks) {\n> +\tif (repo_trust_executable_bit(the_repository) && has_symlinks) {\n>   \t\tce->ce_mode = create_ce_mode(st_mode);\n>   \t} else {\n>   \t\t/* If there is an existing entry, pick the mode bits and type\n\nThere seem to be typos everywhere here, will change in the next reroll.\n\nRegards, yuchen\n\n"},{"id":"545346","messageId":"CAP8UFD2Y+JO06MLcpk5tXU_MMcPeAxvuiys=ytoqY5vkB1t0xQ@mail.gmail.com","threadId":"65718","inReplyTo":"20260610093635.139719-3-cat@malon.dev","subject":"Re: [PATCH v2 2/3] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2026-06-12T07:43:23Z","receivedAt":"2026-06-12T07:43:37Z","isPatch":true,"body":"On Wed, Jun 10, 2026 at 11:37 AM Tian Yuchen <cat@malon.dev> wrote:\n>\n> The ce_mode_from_stat() function is declared as a static inline function\n> in 'read-cache.h'. As we want to migrate configuration variables, this\n> helper function will need access to corresponding repository-specific\n> configuration logic. Move the implementation to 'read-cache.c' to\n> cleanly encapsulate its dependencies.\n>\n> Note that the 'extern int trust_executable_bit, has_symlinks;' line is\n> discarded because it's not necessary when the function lives in\n> \"read-cache.c\".\n>\n> At present, this change has no visible impact, but it is crucial\n> for our future plans to pass in the repo context. Comment\n> has been added whilst we are at it.\n\nWe prefer it when comments like the one below are added in front of\nthe declaration of the function into the header file (\"read-cache.h\"\nhere), rather than the *.c file.\n\nAnd yeah, I know that \"read-cache.h\" is a bad example right now\nbecause no function has such a comment there yet.\n\n> +/*\n> + * Determine the appropriate index mode for a file based on its stat()\n> + * information and the existing cache entry (if any).\n> + *\n> + * This function handles degradation for filesystems that lack\n> + * symlink support or reliable executable bits.\n> + */\n> +unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n> +{\n> +       if (!has_symlinks && S_ISREG(mode) &&\n> +           ce && S_ISLNK(ce->ce_mode))\n> +               return ce->ce_mode;\n> +       if (!trust_executable_bit && S_ISREG(mode)) {\n> +               if (ce && S_ISREG(ce->ce_mode))\n> +                       return ce->ce_mode;\n> +               return create_ce_mode(0666);\n> +       }\n> +       return create_ce_mode(mode);\n> +}\n"},{"id":"545347","messageId":"CAP8UFD0X48BJcjLrr8mY0x3A03NSEN35G7jrvdvvp7Qm5PYAdw@mail.gmail.com","threadId":"65718","inReplyTo":"20260610093635.139719-4-cat@malon.dev","subject":"Re: [PATCH v2 3/3] environment: move trust_executable_bit into repo_config_values","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2026-06-12T07:48:37Z","receivedAt":"2026-06-12T07:48:49Z","isPatch":true,"body":"On Wed, Jun 10, 2026 at 11:37 AM Tian Yuchen <cat@malon.dev> wrote:\n\n> +/*\n> + * Getters for the `repo_trust_executable_bit` fields of `struct repo_config_values`.\n\ns/Getters/Getter/\ns/fields/field/\ns/repo_trust_executable_bit/trust_executable_bit/\n\n> + * They check `repo->gitdir` to prevent calling repo_config_values()\n> + * before the configuration is loaded or in bare environments.\n> + */\n> +int repo_trust_executable_bit(struct repository *repo);\n\nThanks.\n"},{"id":"545393","messageId":"xmqqv7bnhjhv.fsf@gitster.g","threadId":"65718","inReplyTo":"CAP8UFD0X48BJcjLrr8mY0x3A03NSEN35G7jrvdvvp7Qm5PYAdw@mail.gmail.com","subject":"Re: [PATCH v2 3/3] environment: move trust_executable_bit into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-12T15:21:48Z","receivedAt":"2026-06-12T15:21:52Z","isPatch":true,"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Wed, Jun 10, 2026 at 11:37 AM Tian Yuchen <cat@malon.dev> wrote:\n>\n>> +/*\n>> + * Getters for the `repo_trust_executable_bit` fields of `struct repo_config_values`.\n>\n> s/Getters/Getter/\n> s/fields/field/\n> s/repo_trust_executable_bit/trust_executable_bit/\n>\n>> + * They check `repo->gitdir` to prevent calling repo_config_values()\n>> + * before the configuration is loaded or in bare environments.\n>> + */\n>> +int repo_trust_executable_bit(struct repository *repo);\n>\n> Thanks.\n\nThanks.  \n\nA hopefully small and final reroll is in order, and\n<21f74852-2209-4d77-94f4-b2b9412eb8e0@malon.dev> has already\npromised one.\n\n"},{"id":"545398","messageId":"20260612160527.167203-1-cat@malon.dev","threadId":"65718","inReplyTo":"20260610093635.139719-1-cat@malon.dev","subject":"[PATCH v3 0/3] environment: migrate 'trust_executable_bit' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-12T16:05:23Z","receivedAt":"2026-06-12T16:05:47Z","isPatch":true,"body":"The 'core.filemode' (stored as 'trust_executable_bit') configuration\nact as a core filesystem capability flag.\n\nThis series moves it into 'struct repo_config_values' to tie it to\nthe specific repository instance it was read from. Eager parsing\nis maintained because this flag is heavily consulted in hot paths.\n\nNote: 'repo_config_values()' still does not support any struct\nrepository other than the_repository due to how deeply these flags\nare accessed. In other words, this series of patches is laying\nthe groundwork for the eventual elimination of the_repository.\n\nPrevious related work:\n[PATCH 2/6] config: add trust_executable_bit to global config [1]\n[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]\n(This previous attempt was unsuccessful because the target location\nselected was 'struct repo_settings', which our analysis indicated\nwas not the optimal choice. For further details, please see: [3])\n\nChanges since V2:\n\nFixed a whole bunch of typos;\n\nMoved the comment for 'ce_mode_from_stat()' from 'read-cache.c' to\n'read-cache.h'.\n\nThanks!\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\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 (3):\n  read-cache: remove redundant extern declarations\n  read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'\n  environment: move trust_executable_bit into repo_config_values\n\n apply.c       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  9 ++++++++-\n read-cache.c  | 21 ++++++++++++++++-----\n read-cache.h  | 23 +++++++++--------------\n 5 files changed, 43 insertions(+), 23 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"545397","messageId":"20260612160527.167203-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260612160527.167203-1-cat@malon.dev","subject":"[PATCH v3 2/3] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-12T16:05:25Z","receivedAt":"2026-06-12T16:05:48Z","isPatch":true,"body":"The ce_mode_from_stat() function is declared as a static inline function\nin 'read-cache.h'. As we want to migrate configuration variables, this\nhelper function will need access to corresponding repository-specific\nconfiguration logic. Move the implementation to 'read-cache.c' to\ncleanly encapsulate its dependencies.\n\nNote that the 'extern int trust_executable_bit, has_symlinks;' line is\ndiscarded because it's not necessary when the function lives in\n\"read-cache.c\".\n\nAt present, this change has no visible impact, but it is crucial\nfor our future plans to pass in the repo context. Comment\nhas been added whilst 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 read-cache.c | 13 +++++++++++++\n read-cache.h | 23 +++++++++--------------\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..54150fe756 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -202,6 +202,19 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \t}\n }\n \n+unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n+{\n+\tif (!has_symlinks && S_ISREG(mode) &&\n+\t    ce && S_ISLNK(ce->ce_mode))\n+\t\treturn ce->ce_mode;\n+\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\t\tif (ce && S_ISREG(ce->ce_mode))\n+\t\t\treturn ce->ce_mode;\n+\t\treturn create_ce_mode(0666);\n+\t}\n+\treturn create_ce_mode(mode);\n+}\n+\n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..9088a0724a 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -5,20 +5,15 @@\n #include \"object.h\"\n #include \"pathspec.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n-\t\t\t\t\t     unsigned int mode)\n-{\n-\textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n-\t    ce && S_ISLNK(ce->ce_mode))\n-\t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n-\t\tif (ce && S_ISREG(ce->ce_mode))\n-\t\t\treturn ce->ce_mode;\n-\t\treturn create_ce_mode(0666);\n-\t}\n-\treturn create_ce_mode(mode);\n-}\n+/*\n+ * Determine the appropriate index mode for a file based on its stat()\n+ * information and the existing cache entry (if any).\n+ *\n+ * This function handles degradation for filesystems that lack\n+ * symlink support or reliable executable bits.\n+ */\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+\t\t\t\tunsigned int mode);\n \n static inline int ce_to_dtype(const struct cache_entry *ce)\n {\n-- \n2.43.0\n\n"},{"id":"545399","messageId":"20260612160527.167203-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260612160527.167203-1-cat@malon.dev","subject":"[PATCH v3 1/3] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-12T16:05:24Z","receivedAt":"2026-06-12T16:05:48Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"545400","messageId":"20260612160527.167203-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260612160527.167203-1-cat@malon.dev","subject":"[PATCH v3 3/3] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-12T16:05:26Z","receivedAt":"2026-06-12T16:05:56Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configuration\ninto the repository-specific 'repo_config_values'\nstruct. To ensure code readability, the getter function\n'repo_trust_executable_bit()' has been introduced.\n\nFor now, associated functions access this configuration by\nexplicitly falling back to '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 apply.c       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  9 ++++++++-\n read-cache.c  |  8 ++++----\n 4 files changed, 22 insertions(+), 8 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..fbb907d3c0 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (repo_trust_executable_bit(the_repository) || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..75069a884d 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -142,6 +141,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+int repo_trust_executable_bit(struct repository *repo)\n+{\n+\treturn repo->gitdir?\n+\t\trepo_config_values(repo)->trust_executable_bit :\n+\t\t1;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..d602d2cd95 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 \tint apply_sparse_checkout;\n+\tint trust_executable_bit;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -123,6 +124,13 @@ 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+ * Getter for the `trust_executable_bit` field of `struct repo_config_values`.\n+ * It checks `repo->gitdir` to prevent calling repo_config_values()\n+ * before the configuration is loaded or in bare environments.\n+ */\n+int repo_trust_executable_bit(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -160,7 +168,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex 54150fe756..757249a449 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -207,7 +207,7 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n \tif (!has_symlinks && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n@@ -221,7 +221,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -331,7 +331,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (repo_trust_executable_bit(the_repository) &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -752,7 +752,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (repo_trust_executable_bit(the_repository) && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"545989","messageId":"20260619162105.648495-1-cat@malon.dev","threadId":"65718","inReplyTo":"20260612160527.167203-1-cat@malon.dev","subject":"[PATCH v4 0/3] environment: migrate 'trust_executable_bit' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-19T16:21:02Z","receivedAt":"2026-06-19T16:21:15Z","isPatch":true,"body":"The 'core.filemode' (stored as 'trust_executable_bit') configuration\nact as a core filesystem capability flag.\n\nThis series moves it into 'struct repo_config_values' to tie it to\nthe specific repository instance it was read from. Eager parsing\nis maintained because this flag is heavily consulted in hot paths.\n\nNote: 'repo_config_values()' still does not support any struct\nrepository other than the_repository due to how deeply these flags\nare accessed. In other words, this series of patches is laying\nthe groundwork for the eventual elimination of the_repository.\n\nPrevious related work:\n[PATCH 2/6] config: add trust_executable_bit to global config [1]\n[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]\n(This previous attempt was unsuccessful because the target location\nselected was 'struct repo_settings', which our analysis indicated\nwas not the optimal choice. For further details, please see: [3])\n\nChange since V3:\n\n - In repo_trust_executable_bit(), change repo->gitdir to using\n (repo && repo->initialized).\n\nThanks!\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\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 (3):\n  read-cache: remove redundant extern declarations\n  read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'\n  environment: move trust_executable_bit into repo_config_values\n\n apply.c       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  9 ++++++++-\n read-cache.c  | 21 ++++++++++++++++-----\n read-cache.h  | 23 +++++++++--------------\n 5 files changed, 43 insertions(+), 23 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"545990","messageId":"20260619162105.648495-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260619162105.648495-1-cat@malon.dev","subject":"[PATCH v4 1/3] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-19T16:21:03Z","receivedAt":"2026-06-19T16:21:17Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"545991","messageId":"20260619162105.648495-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260619162105.648495-1-cat@malon.dev","subject":"[PATCH v4 2/3] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-19T16:21:04Z","receivedAt":"2026-06-19T16:21:20Z","isPatch":true,"body":"The ce_mode_from_stat() function is declared as a static inline function\nin 'read-cache.h'. As we want to migrate configuration variables, this\nhelper function will need access to corresponding repository-specific\nconfiguration logic. Move the implementation to 'read-cache.c' to\ncleanly encapsulate its dependencies.\n\nNote that the 'extern int trust_executable_bit, has_symlinks;' line is\ndiscarded because it's not necessary when the function lives in\n\"read-cache.c\".\n\nAt present, this change has no visible impact, but it is crucial\nfor our future plans to pass in the repo context. Comment\nhas been added whilst 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 read-cache.c | 13 +++++++++++++\n read-cache.h | 23 +++++++++--------------\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..54150fe756 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -202,6 +202,19 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \t}\n }\n \n+unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n+{\n+\tif (!has_symlinks && S_ISREG(mode) &&\n+\t    ce && S_ISLNK(ce->ce_mode))\n+\t\treturn ce->ce_mode;\n+\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\t\tif (ce && S_ISREG(ce->ce_mode))\n+\t\t\treturn ce->ce_mode;\n+\t\treturn create_ce_mode(0666);\n+\t}\n+\treturn create_ce_mode(mode);\n+}\n+\n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..9088a0724a 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -5,20 +5,15 @@\n #include \"object.h\"\n #include \"pathspec.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n-\t\t\t\t\t     unsigned int mode)\n-{\n-\textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n-\t    ce && S_ISLNK(ce->ce_mode))\n-\t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n-\t\tif (ce && S_ISREG(ce->ce_mode))\n-\t\t\treturn ce->ce_mode;\n-\t\treturn create_ce_mode(0666);\n-\t}\n-\treturn create_ce_mode(mode);\n-}\n+/*\n+ * Determine the appropriate index mode for a file based on its stat()\n+ * information and the existing cache entry (if any).\n+ *\n+ * This function handles degradation for filesystems that lack\n+ * symlink support or reliable executable bits.\n+ */\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+\t\t\t\tunsigned int mode);\n \n static inline int ce_to_dtype(const struct cache_entry *ce)\n {\n-- \n2.43.0\n\n"},{"id":"545992","messageId":"20260619162105.648495-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260619162105.648495-1-cat@malon.dev","subject":"[PATCH v4 3/3] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-06-19T16:21:05Z","receivedAt":"2026-06-19T16:21:24Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configuration\ninto the repository-specific 'repo_config_values'\nstruct. To ensure code readability, the getter function\n'repo_trust_executable_bit()' has been introduced.\n\nFor now, associated functions access this configuration by\nexplicitly falling back to '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 apply.c       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  9 ++++++++-\n read-cache.c  |  8 ++++----\n 4 files changed, 22 insertions(+), 8 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..fbb907d3c0 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (repo_trust_executable_bit(the_repository) || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..348053146e 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -142,6 +141,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+int repo_trust_executable_bit(struct repository *repo)\n+{\n+\treturn (repo && repo->initialized) ?\n+\t\trepo_config_values(repo)->trust_executable_bit :\n+\t\t1;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..6607ac9408 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 \tint apply_sparse_checkout;\n+\tint trust_executable_bit;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -123,6 +124,13 @@ 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+ * Getter for the `trust_executable_bit` field of `struct repo_config_values`.\n+ * It checks `repo->initialized` to prevent calling repo_config_values()`\n+ * before the repository setup is fully complete or in non-git environments.\n+ */\n+int repo_trust_executable_bit(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -160,7 +168,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex 54150fe756..757249a449 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -207,7 +207,7 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n \tif (!has_symlinks && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n@@ -221,7 +221,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -331,7 +331,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (repo_trust_executable_bit(the_repository) &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -752,7 +752,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (repo_trust_executable_bit(the_repository) && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"546712","messageId":"xmqqcxx9ukvw.fsf@gitster.g","threadId":"65718","inReplyTo":"20260619162105.648495-1-cat@malon.dev","subject":"Re: [PATCH v4 0/3] environment: migrate 'trust_executable_bit' into 'repo_config_values'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-06-29T20:55:31Z","receivedAt":"2026-06-29T20:55:34Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> The 'core.filemode' (stored as 'trust_executable_bit') configuration\n> act as a core filesystem capability flag.\n\nThis unfortunately hasn't heard any responses since June 19th.  Are\nthere remaining issues with it?  Or do people fundamentally have\nobjections against this change?  Or things are too busy in general\nthat there are more patches than there are folks willing to review\nthem?\n"},{"id":"548194","messageId":"ba63b45c-b078-4d12-b699-ee4f57549c40@malon.dev","threadId":"65718","inReplyTo":"xmqqcxx9ukvw.fsf@gitster.g","subject":"Re: [PATCH v4 0/3] environment: migrate 'trust_executable_bit' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T03:28:48Z","receivedAt":"2026-07-15T03:28:56Z","isPatch":true,"body":"On 6/30/26 04:55, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> The 'core.filemode' (stored as 'trust_executable_bit') configuration\n>> act as a core filesystem capability flag.\n> \n> This unfortunately hasn't heard any responses since June 19th.  Are\n> there remaining issues with it?  Or do people fundamentally have\n> objections against this change?  Or things are too busy in general\n> that there are more patches than there are folks willing to review\n> them?\n\nSeems that no many people are viewing this. Let me send V5 which \nincludes a new commit and a few changes.\n\nHope this helps.\n\nThanks, yuchen\n"},{"id":"548197","messageId":"20260715035501.48271-1-cat@malon.dev","threadId":"65718","inReplyTo":"20260619162105.648495-1-cat@malon.dev","subject":"[PATCH v5 0/4] environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T03:54:57Z","receivedAt":"2026-07-15T03:55:08Z","isPatch":true,"body":"This series moves 'trust_executable_bit' and 'has_symlinks' into\n'struct repo_config_values' to tie them to the specific repository\ninstance they were read from. Eager parsing is maintained because\nthese two flags are heavily consulted in hot paths.\n\nNote: 'repo_config_values()' still does not support any struct\nrepository other than the_repository due to how deeply these flags\nare accessed. In other words, this series of patches is laying\nthe groundwork for the eventual elimination of the_repository.\n\nPrevious related work:\n\n[PATCH 2/6] config: add trust_executable_bit to global config [1]\n[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]\n(This previous attempt was unsuccessful because the target location\nselected was 'struct repo_settings', which our analysis indicated\nwas not the optimal choice. For further details, please see: [3])\n\n[PATCH 5/6] config: move has_symlinks [4]\n\nRFC:\n\n - I wonder if there is a better way to deal with compat/mingw.c in\n commit 4/4. Is it possible not to introduce a fallback variable?\n\nChange since V4:\n\n - the migration of has_symlinks is back [5], as a commit 4/4;\n\n - drop the comment for repo_executable_bit();\n\n - do not always pass the_repository to the getters. Use 'repo' when possible. \n\nThanks!\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\n[4] https://lore.kernel.org/git/a154008619790f7a60f2bba91db7b0fe29e67e1a.1685716420.git.gitgitgadget@gmail.com/\n[5] https://lore.kernel.org/git/xmqq7bokebct.fsf@gitster.g/\n\nTian Yuchen (4):\n  read-cache: remove redundant extern declarations\n  read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'\n  environment: move trust_executable_bit into repo_config_values\n  environment: move has_symlinks into repo_config_values\n\n apply.c        |  4 ++--\n combine-diff.c |  2 +-\n compat/mingw.c |  7 ++++---\n entry.c        |  2 +-\n environment.c  | 28 ++++++++++++++++++++++++----\n environment.h  |  9 +++++++--\n read-cache.c   | 33 ++++++++++++++++++++++++++-------\n read-cache.h   | 16 ++--------------\n 8 files changed, 67 insertions(+), 34 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"548198","messageId":"20260715035501.48271-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260715035501.48271-1-cat@malon.dev","subject":"[PATCH v5 1/4] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T03:54:58Z","receivedAt":"2026-07-15T03:55:12Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"548199","messageId":"20260715035501.48271-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260715035501.48271-1-cat@malon.dev","subject":"[PATCH v5 2/4] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T03:54:59Z","receivedAt":"2026-07-15T03:55:15Z","isPatch":true,"body":"The ce_mode_from_stat() function is declared as a static inline function\nin 'read-cache.h'. As we want to migrate configuration variables, this\nhelper function will need access to corresponding repository-specific\nconfiguration logic. Move the implementation to 'read-cache.c' to\ncleanly encapsulate its dependencies.\n\nNote that the 'extern int trust_executable_bit, has_symlinks;' line is\ndiscarded because it's not necessary when the function lives in\n\"read-cache.c\".\n\nAt present, this change has no visible impact, but it is crucial\nfor our future plans to pass in the repo context. Comment\nhas been added whilst 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 read-cache.c | 20 ++++++++++++++++++++\n read-cache.h | 16 ++--------------\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..cb4f4878c8 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -202,6 +202,26 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \t}\n }\n \n+/*\n+ * Determine the appropriate index mode for a file based on its stat()\n+ * information and the existing cache entry (if any).\n+ *\n+ * This function handles degradation for filesystems that lack\n+ * symlink support or reliable executable bits.\n+ */\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n+{\n+\tif (!has_symlinks && S_ISREG(mode) &&\n+\t    ce && S_ISLNK(ce->ce_mode))\n+\t\treturn ce->ce_mode;\n+\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\t\tif (ce && S_ISREG(ce->ce_mode))\n+\t\t\treturn ce->ce_mode;\n+\t\treturn create_ce_mode(0666);\n+\t}\n+\treturn create_ce_mode(mode);\n+}\n+\n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..3c4af2faeb 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -5,20 +5,8 @@\n #include \"object.h\"\n #include \"pathspec.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n-\t\t\t\t\t     unsigned int mode)\n-{\n-\textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n-\t    ce && S_ISLNK(ce->ce_mode))\n-\t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n-\t\tif (ce && S_ISREG(ce->ce_mode))\n-\t\t\treturn ce->ce_mode;\n-\t\treturn create_ce_mode(0666);\n-\t}\n-\treturn create_ce_mode(mode);\n-}\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+\t\t\t\tunsigned int mode);\n \n static inline int ce_to_dtype(const struct cache_entry *ce)\n {\n-- \n2.43.0\n\n"},{"id":"548200","messageId":"20260715035501.48271-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260715035501.48271-1-cat@malon.dev","subject":"[PATCH v5 3/4] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T03:55:00Z","receivedAt":"2026-07-15T03:55:19Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configuration\ninto the repository-specific 'repo_config_values'\nstruct.\n\nTo ensure code readability, the getter function\n'repo_trust_executable_bit()' has been introduced.\nCallers access this configuration by passing in 'repo'\nwhen possible, and explicitly fall back to 'the_repository'\nthe rest of time.\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       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  4 +++-\n read-cache.c  |  8 ++++----\n 4 files changed, 17 insertions(+), 8 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..47b6ae5904 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..75069a884d 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -142,6 +141,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+int repo_trust_executable_bit(struct repository *repo)\n+{\n+\treturn repo->gitdir?\n+\t\trepo_config_values(repo)->trust_executable_bit :\n+\t\t1;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..72b59fd89c 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 \tint apply_sparse_checkout;\n+\tint trust_executable_bit;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -123,6 +124,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+int repo_trust_executable_bit(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -160,7 +163,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex cb4f4878c8..a9c11a3346 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -214,7 +214,7 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n \tif (!has_symlinks && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n@@ -228,7 +228,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -338,7 +338,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (repo_trust_executable_bit(the_repository) &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -759,7 +759,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"548201","messageId":"20260715035501.48271-5-cat@malon.dev","threadId":"65718","inReplyTo":"20260715035501.48271-1-cat@malon.dev","subject":"[PATCH v5 4/4] environment: move has_symlinks into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-15T03:55:01Z","receivedAt":"2026-07-15T03:55:22Z","isPatch":true,"body":"Move the global 'has_symlinks' configuration into the\nrepository-specific 'repo_config_values' struct.\n\nTo ensure code readability, the getter function\n'repo_has_symlinks()' has been introduced. Callers access\nthis configuration by passing in 'repo' when possible,\nand explicitly fall back to 'the_repository' the rest\nof the time.\n\nNote:\nTo support early platform-specific (MinGW) overrides\nbefore repository initialization, a global variable\n'default_has_symlinks' fallback is introduced as a fallback\nin environment.h. The *writer* in compat/mingw.c can only\naccess this variable.\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        |  2 +-\n combine-diff.c |  2 +-\n compat/mingw.c |  7 ++++---\n entry.c        |  2 +-\n environment.c  | 17 +++++++++++++++--\n environment.h  |  5 ++++-\n read-cache.c   |  9 +++++----\n 7 files changed, 31 insertions(+), 13 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 47b6ae5904..4ce4160b48 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -4511,7 +4511,7 @@ static int try_create_file(struct apply_state *state, const char *path,\n \t\treturn !!mkdir(path, 0777);\n \t}\n \n-\tif (has_symlinks && S_ISLNK(mode))\n+\tif (repo_has_symlinks(state->repo) && S_ISLNK(mode))\n \t\t/* Although buf:size is counted string, it also is NUL\n \t\t * terminated.\n \t\t */\ndiff --git a/combine-diff.c b/combine-diff.c\nindex b799862068..80e5c46e9b 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -1078,7 +1078,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\t/* if symlinks don't work, assume symlink if all parents\n \t\t\t * are symlinks\n \t\t\t */\n-\t\t\tis_file = has_symlinks;\n+\t\t\tis_file = repo_has_symlinks(rev->repo);\n \t\t\tfor (i = 0; !is_file && i < num_parent; i++)\n \t\t\t\tis_file = !S_ISLNK(elem->parent[i].mode);\n \t\t\tif (!is_file)\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex aa7525f419..b38cd9a1ec 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -7,6 +7,7 @@\n #include \"config.h\"\n #include \"dir.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"gettext.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n@@ -1043,7 +1044,7 @@ int mingw_chdir(const char *dirname)\n \tif (xutftowcs_path(wdirname, dirname) < 0)\n \t\treturn -1;\n \n-\tif (has_symlinks) {\n+\tif (repo_has_symlinks(the_repository)) {\n \t\tHANDLE hnd = CreateFileW(wdirname, 0,\n \t\t\t\tFILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,\n \t\t\t\tOPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);\n@@ -2903,7 +2904,7 @@ int symlink(const char *target, const char *link)\n \tint len;\n \n \t/* fail if symlinks are disabled or API is not supported (WinXP) */\n-\tif (!has_symlinks) {\n+\tif (!repo_has_symlinks(the_repository)) {\n \t\terrno = ENOSYS;\n \t\treturn -1;\n \t}\n@@ -3181,7 +3182,7 @@ static void setup_windows_environment(void)\n \t * symlink support.\n \t */\n \tif (!(tmp = getenv(\"MSYS\")) || !strstr(tmp, \"winsymlinks:nativestrict\"))\n-\t\thas_symlinks = 0;\n+\t\tdefault_has_symlinks = 0;\n }\n \n static void get_current_user_sid(PSID *sid, HANDLE *linked_token)\ndiff --git a/entry.c b/entry.c\nindex 7817aee362..f2854b4cd8 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -321,7 +321,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca\n \t\t * We can't make a real symlink; write out a regular file entry\n \t\t * with the symlink destination as its contents.\n \t\t */\n-\t\tif (!has_symlinks || to_tempfile)\n+\t\tif (!repo_has_symlinks(state->istate ? state->istate->repo : NULL) || to_tempfile)\n \t\t\tgoto write_file_entry;\n \n \t\tret = symlink(new_blob, path);\ndiff --git a/environment.c b/environment.c\nindex 75069a884d..c7cebe18fd 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -43,7 +43,7 @@ static int zlib_compression_seen;\n \n int trust_ctime = 1;\n int check_stat = 1;\n-int has_symlinks = 1;\n+int default_has_symlinks = 1;\n int minimum_abbrev = 4, default_abbrev = -1;\n int ignore_case;\n int assume_unchanged;\n@@ -148,6 +148,17 @@ int repo_trust_executable_bit(struct repository *repo)\n \t\t1;\n }\n \n+int repo_has_symlinks(struct repository *repo)\n+{\n+\tif (!repo)\n+\t\trepo = the_repository;\n+\n+\tif (!repo->gitdir)\n+\t\treturn default_has_symlinks;\n+\n+\treturn repo_config_values(repo)->has_symlinks;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -336,7 +347,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.symlinks\")) {\n-\t\thas_symlinks = git_config_bool(var, value);\n+\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\t\tcfg->has_symlinks = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n@@ -727,5 +739,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->trust_executable_bit = 1;\n+\tcfg->has_symlinks = default_has_symlinks;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 72b59fd89c..e590225c86 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tint apply_sparse_checkout;\n \tint trust_executable_bit;\n+\tint has_symlinks;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -126,6 +127,8 @@ int git_default_core_config(const char *var, const char *value,\n \n int repo_trust_executable_bit(struct repository *repo);\n \n+int repo_has_symlinks(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -165,7 +168,7 @@ extern char *git_work_tree_cfg;\n /* Environment bits from configuration mechanism */\n extern int trust_ctime;\n extern int check_stat;\n-extern int has_symlinks;\n+extern int default_has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\ndiff --git a/read-cache.c b/read-cache.c\nindex a9c11a3346..5a40ffa061 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -211,7 +211,7 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n  */\n unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n {\n-\tif (!has_symlinks && S_ISREG(mode) &&\n+\tif (!repo_has_symlinks(the_repository) && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n \tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n@@ -226,7 +226,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n-\t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n+\t\treturn repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n \t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n@@ -344,7 +344,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\tbreak;\n \tcase S_IFLNK:\n \t\tif (!S_ISLNK(st->st_mode) &&\n-\t\t    (has_symlinks || !S_ISREG(st->st_mode)))\n+\t\t    (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode)))\n \t\t\tchanged |= TYPE_CHANGED;\n \t\tbreak;\n \tcase S_IFGITLINK:\n@@ -759,7 +759,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) &&\n+\t    repo_has_symlinks(istate->repo)) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"548211","messageId":"CAP8UFD2=FbbnCqWkTLEGBpz=90sh=j_70h2UJR=p4uj6u3tqMQ@mail.gmail.com","threadId":"65718","inReplyTo":"20260715035501.48271-5-cat@malon.dev","subject":"Re: [PATCH v5 4/4] environment: move has_symlinks into repo_config_values","fromName":"Christian Couder","fromEmail":"christian.couder@gmail.com","sentAt":"2026-07-15T06:21:40Z","receivedAt":"2026-07-15T06:21:52Z","isPatch":true,"body":"On Wed, Jul 15, 2026 at 5:55 AM Tian Yuchen <cat@malon.dev> wrote:\n>\n> Move the global 'has_symlinks' configuration into the\n> repository-specific 'repo_config_values' struct.\n>\n> To ensure code readability, the getter function\n> 'repo_has_symlinks()' has been introduced. Callers access\n> this configuration by passing in 'repo' when possible,\n> and explicitly fall back to 'the_repository' the rest\n> of the time.\n>\n> Note:\n> To support early platform-specific (MinGW) overrides\n> before repository initialization, a global variable\n> 'default_has_symlinks' fallback is introduced as a fallback\n\nIt seems a bit redundant to use \"fallback\" twice in the above sentence.\n\n> in environment.h. The *writer* in compat/mingw.c can only\n> access this variable.\n\nOtherwise this series looks good to me.\n\nThanks.\n"},{"id":"548307","messageId":"xmqqtsq0npd4.fsf@gitster.g","threadId":"65718","inReplyTo":"CAP8UFD2=FbbnCqWkTLEGBpz=90sh=j_70h2UJR=p4uj6u3tqMQ@mail.gmail.com","subject":"Re: [PATCH v5 4/4] environment: move has_symlinks into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T17:18:15Z","receivedAt":"2026-07-15T17:18:18Z","isPatch":true,"body":"Christian Couder <christian.couder@gmail.com> writes:\n\n> On Wed, Jul 15, 2026 at 5:55 AM Tian Yuchen <cat@malon.dev> wrote:\n>>\n>> Move the global 'has_symlinks' configuration into the\n>> repository-specific 'repo_config_values' struct.\n>>\n>> To ensure code readability, the getter function\n>> 'repo_has_symlinks()' has been introduced. Callers access\n>> this configuration by passing in 'repo' when possible,\n>> and explicitly fall back to 'the_repository' the rest\n>> of the time.\n>>\n>> Note:\n>> To support early platform-specific (MinGW) overrides\n>> before repository initialization, a global variable\n>> 'default_has_symlinks' fallback is introduced as a fallback\n>\n> It seems a bit redundant to use \"fallback\" twice in the above sentence.\n>\n>> in environment.h. The *writer* in compat/mingw.c can only\n>> access this variable.\n>\n> Otherwise this series looks good to me.\n\nThanks for helping, Christian, and thanks, Tian, for working on this\ntopic.\n\n"},{"id":"548313","messageId":"xmqqwluwm7sp.fsf@gitster.g","threadId":"65718","inReplyTo":"20260715035501.48271-5-cat@malon.dev","subject":"Re: [PATCH v5 4/4] environment: move has_symlinks into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-15T18:23:02Z","receivedAt":"2026-07-15T18:23:06Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> Note:\n> To support early platform-specific (MinGW) overrides\n> before repository initialization, a global variable\n> 'default_has_symlinks' fallback is introduced as a fallback\n> in environment.h. The *writer* in compat/mingw.c can only\n> access this variable.\n\nThis may invite people to abuse the global variable.  I wonder if we\nwant to do something similar to how we handle is_dir_sep() and\nfriends instead.\n\nThe idea is to have something like this in the generic header:\n\n        #ifndef platform_has_symlinks\n        #define platform_has_symlinks() 1\n        #endif\n\nAnd then allow selected platforms override it:\n\n        /* in compat/mingw.h */\n        #define platform_has_symlinks() mingw_platform_has_symlinks()\n        extern int mingw_platform_has_symlinks(void);\n\n        /* in compat/mingw.c */\n        int mingw_platform_has_symlinks(void)\n        {\n                if (!(tmp = getenv(\"MSYS\")) || !strstr(tmp, \"winsymlinks:nativestrict\"))\n                        return 0;\n                else\n                        return 1;\n        }\n\nThis keeps the namespace clean and avoids exposing a mutable state\nvariable that others might be tempted to meddle with.\n"},{"id":"548380","messageId":"20260716084941.1101918-1-cat@malon.dev","threadId":"65718","inReplyTo":"20260715035501.48271-1-cat@malon.dev","subject":"[PATCH v6 0/4] environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-16T08:49:37Z","receivedAt":"2026-07-16T08:49:54Z","isPatch":true,"body":"This series moves 'trust_executable_bit' and 'has_symlinks' into\n'struct repo_config_values' to tie them to the specific repository\ninstance they were read from. Eager parsing is maintained because\nthese two flags are heavily consulted in hot paths.\n\nNote: 'repo_config_values()' still does not support any struct\nrepository other than the_repository due to how deeply these flags\nare accessed. In other words, this series of patches is laying\nthe groundwork for the eventual elimination of the_repository.\n\nPrevious related work:\n\n[PATCH 2/6] config: add trust_executable_bit to global config [1]\n[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]\n(This previous attempt was unsuccessful because the target location\nselected was 'struct repo_settings', which our analysis indicated\nwas not the optimal choice. For further details, please see: [3])\n\n[PATCH 5/6] config: move has_symlinks [4]\n\nRFC:\n\n - Is the locations of the newly introduced definitions/macros\n appropriate?\n\nChange since V5:\n\n - do not intruduce new global variable to deal with compat/mingw.c.\n Make use of macro preprocessing to allow platforms to override\n platform_has_symlinks().\n\nThanks!\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\n[4] https://lore.kernel.org/git/a154008619790f7a60f2bba91db7b0fe29e67e1a.1685716420.git.gitgitgadget@gmail.com/\n[5] https://lore.kernel.org/git/xmqq7bokebct.fsf@gitster.g/\n\nTian Yuchen (4):\n  read-cache: remove redundant extern declarations\n  read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'\n  environment: move trust_executable_bit into repo_config_values\n  environment: move has_symlinks into repo_config_values\n\n apply.c           |  4 ++--\n combine-diff.c    |  2 +-\n compat/mingw.c    | 17 +++++++++++++----\n compat/mingw.h    |  3 +++\n entry.c           |  2 +-\n environment.c     | 27 +++++++++++++++++++++++----\n environment.h     |  8 ++++++--\n git-compat-util.h |  4 ++++\n read-cache.c      | 33 ++++++++++++++++++++++++++-------\n read-cache.h      | 16 ++--------------\n 10 files changed, 81 insertions(+), 35 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"548381","messageId":"20260716084941.1101918-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260716084941.1101918-1-cat@malon.dev","subject":"[PATCH v6 1/4] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-16T08:49:38Z","receivedAt":"2026-07-16T08:49:59Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"548382","messageId":"20260716084941.1101918-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260716084941.1101918-1-cat@malon.dev","subject":"[PATCH v6 2/4] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-16T08:49:39Z","receivedAt":"2026-07-16T08:50:03Z","isPatch":true,"body":"The ce_mode_from_stat() function is declared as a static inline function\nin 'read-cache.h'. As we want to migrate configuration variables, this\nhelper function will need access to corresponding repository-specific\nconfiguration logic. Move the implementation to 'read-cache.c' to\ncleanly encapsulate its dependencies.\n\nNote that the 'extern int trust_executable_bit, has_symlinks;' line is\ndiscarded because it's not necessary when the function lives in\n\"read-cache.c\".\n\nAt present, this change has no visible impact, but it is crucial\nfor our future plans to pass in the repo context. Comment\nhas been added whilst 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 read-cache.c | 20 ++++++++++++++++++++\n read-cache.h | 16 ++--------------\n 2 files changed, 22 insertions(+), 14 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..cb4f4878c8 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -202,6 +202,26 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \t}\n }\n \n+/*\n+ * Determine the appropriate index mode for a file based on its stat()\n+ * information and the existing cache entry (if any).\n+ *\n+ * This function handles degradation for filesystems that lack\n+ * symlink support or reliable executable bits.\n+ */\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n+{\n+\tif (!has_symlinks && S_ISREG(mode) &&\n+\t    ce && S_ISLNK(ce->ce_mode))\n+\t\treturn ce->ce_mode;\n+\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\t\tif (ce && S_ISREG(ce->ce_mode))\n+\t\t\treturn ce->ce_mode;\n+\t\treturn create_ce_mode(0666);\n+\t}\n+\treturn create_ce_mode(mode);\n+}\n+\n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..3c4af2faeb 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -5,20 +5,8 @@\n #include \"object.h\"\n #include \"pathspec.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n-\t\t\t\t\t     unsigned int mode)\n-{\n-\textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n-\t    ce && S_ISLNK(ce->ce_mode))\n-\t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n-\t\tif (ce && S_ISREG(ce->ce_mode))\n-\t\t\treturn ce->ce_mode;\n-\t\treturn create_ce_mode(0666);\n-\t}\n-\treturn create_ce_mode(mode);\n-}\n+unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+\t\t\t\tunsigned int mode);\n \n static inline int ce_to_dtype(const struct cache_entry *ce)\n {\n-- \n2.43.0\n\n"},{"id":"548383","messageId":"20260716084941.1101918-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260716084941.1101918-1-cat@malon.dev","subject":"[PATCH v6 3/4] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-16T08:49:40Z","receivedAt":"2026-07-16T08:50:09Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configuration\ninto the repository-specific 'repo_config_values'\nstruct.\n\nTo ensure code readability, the getter function\n'repo_trust_executable_bit()' has been introduced.\nCallers access this configuration by passing in 'repo'\nwhen possible, and explicitly fall back to 'the_repository'\nthe rest of time.\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       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  4 +++-\n read-cache.c  |  8 ++++----\n 4 files changed, 17 insertions(+), 8 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..47b6ae5904 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..75069a884d 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -142,6 +141,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+int repo_trust_executable_bit(struct repository *repo)\n+{\n+\treturn repo->gitdir?\n+\t\trepo_config_values(repo)->trust_executable_bit :\n+\t\t1;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..72b59fd89c 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 \tint apply_sparse_checkout;\n+\tint trust_executable_bit;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -123,6 +124,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+int repo_trust_executable_bit(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -160,7 +163,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex cb4f4878c8..a9c11a3346 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -214,7 +214,7 @@ unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n \tif (!has_symlinks && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n@@ -228,7 +228,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -338,7 +338,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (repo_trust_executable_bit(the_repository) &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -759,7 +759,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"548384","messageId":"20260716084941.1101918-5-cat@malon.dev","threadId":"65718","inReplyTo":"20260716084941.1101918-1-cat@malon.dev","subject":"[PATCH v6 4/4] environment: move has_symlinks into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-16T08:49:41Z","receivedAt":"2026-07-16T08:50:10Z","isPatch":true,"body":"Move the global 'has_symlinks' configuration into the\nrepository-specific 'repo_config_values' struct.\n\nTo ensure code readability, the getter function\n'repo_has_symlinks()' has been introduced. Callers access\nthis configuration by passing in 'repo' when possible,\nand explicitly fall back to 'the_repository' the rest\nof the time.\n\nNote:\nTo support platform-specific overrides (MinGW) before\nrepository initialization, the 'platform_has_symlinks()'\nmacro is introduced in git-compat-util.h. Platforms can\noverride this in their respective headers.\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           |  2 +-\n combine-diff.c    |  2 +-\n compat/mingw.c    | 17 +++++++++++++----\n compat/mingw.h    |  3 +++\n entry.c           |  2 +-\n environment.c     | 16 ++++++++++++++--\n environment.h     |  4 +++-\n git-compat-util.h |  4 ++++\n read-cache.c      |  9 +++++----\n 9 files changed, 45 insertions(+), 14 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 47b6ae5904..4ce4160b48 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -4511,7 +4511,7 @@ static int try_create_file(struct apply_state *state, const char *path,\n \t\treturn !!mkdir(path, 0777);\n \t}\n \n-\tif (has_symlinks && S_ISLNK(mode))\n+\tif (repo_has_symlinks(state->repo) && S_ISLNK(mode))\n \t\t/* Although buf:size is counted string, it also is NUL\n \t\t * terminated.\n \t\t */\ndiff --git a/combine-diff.c b/combine-diff.c\nindex b799862068..80e5c46e9b 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -1078,7 +1078,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\t/* if symlinks don't work, assume symlink if all parents\n \t\t\t * are symlinks\n \t\t\t */\n-\t\t\tis_file = has_symlinks;\n+\t\t\tis_file = repo_has_symlinks(rev->repo);\n \t\t\tfor (i = 0; !is_file && i < num_parent; i++)\n \t\t\t\tis_file = !S_ISLNK(elem->parent[i].mode);\n \t\t\tif (!is_file)\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex aa7525f419..4781911929 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -7,6 +7,7 @@\n #include \"config.h\"\n #include \"dir.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"gettext.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n@@ -1043,7 +1044,7 @@ int mingw_chdir(const char *dirname)\n \tif (xutftowcs_path(wdirname, dirname) < 0)\n \t\treturn -1;\n \n-\tif (has_symlinks) {\n+\tif (repo_has_symlinks(the_repository)) {\n \t\tHANDLE hnd = CreateFileW(wdirname, 0,\n \t\t\t\tFILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,\n \t\t\t\tOPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);\n@@ -2903,7 +2904,7 @@ int symlink(const char *target, const char *link)\n \tint len;\n \n \t/* fail if symlinks are disabled or API is not supported (WinXP) */\n-\tif (!has_symlinks) {\n+\tif (!repo_has_symlinks(the_repository)) {\n \t\terrno = ENOSYS;\n \t\treturn -1;\n \t}\n@@ -3173,15 +3174,23 @@ static void setup_windows_environment(void)\n \t\tif (!tmp && (tmp = getenv(\"USERPROFILE\")))\n \t\t\tsetenv(\"HOME\", tmp, 1);\n \t}\n+}\n \n+int mingw_platform_has_symlinks(void)\n+{\n+\tstatic int has_symlinks = -1;\n \t/*\n \t * Change 'core.symlinks' default to false, unless native symlinks are\n \t * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can\n \t * run the test suite (which doesn't obey config files) with or without\n \t * symlink support.\n \t */\n-\tif (!(tmp = getenv(\"MSYS\")) || !strstr(tmp, \"winsymlinks:nativestrict\"))\n-\t\thas_symlinks = 0;\n+\tif (has_symlinks < 0) {\n+\t\tconst char *tmp = getenv(\"MSYS\");\n+\t\thas_symlinks = (tmp && strstr(tmp, \"winsymlinks:nativestrict\")) ? 1 : 0;\n+\t}\n+\n+\treturn has_symlinks;\n }\n \n static void get_current_user_sid(PSID *sid, HANDLE *linked_token)\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 444daedfa5..df02aeb632 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -208,6 +208,9 @@ void open_in_gdb(void);\n  */\n int err_win_to_posix(DWORD winerr);\n \n+int mingw_platform_has_symlinks(void);\n+#define platform_has_symlinks() mingw_platform_has_symlinks()\n+\n #ifndef NO_UNIX_SOCKETS\n int mingw_have_unix_sockets(void);\n #undef have_unix_sockets\ndiff --git a/entry.c b/entry.c\nindex 7817aee362..f2854b4cd8 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -321,7 +321,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca\n \t\t * We can't make a real symlink; write out a regular file entry\n \t\t * with the symlink destination as its contents.\n \t\t */\n-\t\tif (!has_symlinks || to_tempfile)\n+\t\tif (!repo_has_symlinks(state->istate ? state->istate->repo : NULL) || to_tempfile)\n \t\t\tgoto write_file_entry;\n \n \t\tret = symlink(new_blob, path);\ndiff --git a/environment.c b/environment.c\nindex 75069a884d..760689d6e7 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -43,7 +43,6 @@ static int zlib_compression_seen;\n \n int trust_ctime = 1;\n int check_stat = 1;\n-int has_symlinks = 1;\n int minimum_abbrev = 4, default_abbrev = -1;\n int ignore_case;\n int assume_unchanged;\n@@ -148,6 +147,17 @@ int repo_trust_executable_bit(struct repository *repo)\n \t\t1;\n }\n \n+int repo_has_symlinks(struct repository *repo)\n+{\n+\tif (!repo)\n+\t\trepo = the_repository;\n+\n+\tif (!repo->gitdir)\n+\t\treturn platform_has_symlinks();\n+\n+\treturn repo_config_values(repo)->has_symlinks;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -336,7 +346,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.symlinks\")) {\n-\t\thas_symlinks = git_config_bool(var, value);\n+\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\t\tcfg->has_symlinks = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n@@ -727,5 +738,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->trust_executable_bit = 1;\n+\tcfg->has_symlinks = platform_has_symlinks();\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 72b59fd89c..ef64a783b0 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tint apply_sparse_checkout;\n \tint trust_executable_bit;\n+\tint has_symlinks;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -126,6 +127,8 @@ int git_default_core_config(const char *var, const char *value,\n \n int repo_trust_executable_bit(struct repository *repo);\n \n+int repo_has_symlinks(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -165,7 +168,6 @@ extern char *git_work_tree_cfg;\n /* Environment bits from configuration mechanism */\n extern int trust_ctime;\n extern int check_stat;\n-extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 5024814bd4..333a5acf33 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c)\n #define is_dir_sep git_is_dir_sep\n #endif\n \n+#ifndef platform_has_symlinks\n+#define platform_has_symlinks() 1\n+#endif\n+\n #ifndef offset_1st_component\n static inline int git_offset_1st_component(const char *path)\n {\ndiff --git a/read-cache.c b/read-cache.c\nindex a9c11a3346..5a40ffa061 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -211,7 +211,7 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n  */\n unsigned int ce_mode_from_stat(const struct cache_entry *ce, unsigned int mode)\n {\n-\tif (!has_symlinks && S_ISREG(mode) &&\n+\tif (!repo_has_symlinks(the_repository) && S_ISREG(mode) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n \tif (!repo_trust_executable_bit(the_repository) && S_ISREG(mode)) {\n@@ -226,7 +226,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n-\t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n+\t\treturn repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n \t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n@@ -344,7 +344,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\tbreak;\n \tcase S_IFLNK:\n \t\tif (!S_ISLNK(st->st_mode) &&\n-\t\t    (has_symlinks || !S_ISREG(st->st_mode)))\n+\t\t    (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode)))\n \t\t\tchanged |= TYPE_CHANGED;\n \t\tbreak;\n \tcase S_IFGITLINK:\n@@ -759,7 +759,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) &&\n+\t    repo_has_symlinks(istate->repo)) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\n-- \n2.43.0\n\n"},{"id":"548452","messageId":"xmqqa4rqfzys.fsf@gitster.g","threadId":"65718","inReplyTo":"20260716084941.1101918-3-cat@malon.dev","subject":"Re: [PATCH v6 2/4] read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-16T20:20:59Z","receivedAt":"2026-07-16T20:21:02Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> The ce_mode_from_stat() function is declared as a static inline function\n> in 'read-cache.h'. As we want to migrate configuration variables, this\n> helper function will need access to corresponding repository-specific\n> configuration logic. Move the implementation to 'read-cache.c' to\n> cleanly encapsulate its dependencies.\n>\n> Note that the 'extern int trust_executable_bit, has_symlinks;' line is\n> discarded because it's not necessary when the function lives in\n> \"read-cache.c\".\n\nA \"static inline\" function in a header typically signals that\nit is expected to be called from the depths of a performance-\ncritical loop.  I am not sure if this helper is still in such\na hot path, but I wonder if we can keep it inline and still\nteach it to take a \"struct repository *r\" (or whatever extra\nparameter you need) to move these two bits from being global\nvariables to members of a per-repository structure.\n\n"},{"id":"548453","messageId":"xmqq33xifzni.fsf@gitster.g","threadId":"65718","inReplyTo":"20260716084941.1101918-5-cat@malon.dev","subject":"Re: [PATCH v6 4/4] environment: move has_symlinks into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-16T20:27:45Z","receivedAt":"2026-07-16T20:27:47Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> Move the global 'has_symlinks' configuration into the\n> repository-specific 'repo_config_values' struct.\n>\n> To ensure code readability, the getter function\n> 'repo_has_symlinks()' has been introduced. Callers access\n> this configuration by passing in 'repo' when possible,\n> and explicitly fall back to 'the_repository' the rest\n> of the time.\n>\n> Note:\n> To support platform-specific overrides (MinGW) before\n> repository initialization, the 'platform_has_symlinks()'\n> macro is introduced in git-compat-util.h. Platforms can\n> override this in their respective headers.\n\nThis shouldn't be a mere \"Note:\" but should be treated as an\nintegral part of the implementation details.\n\n    Move the global variable 'has_symlinks' into the\n    'repo_config_values struct.\n\n    Introduce 'repo_has_symlinks()' getter for readability,\n    and 'platform_has_symlinks()' macro to allow platform\n    specific customization, primarily to help MinGW.\n\nmay be sufficient.\n\nThe changes to compat/ looked reasonable to me.\n\nThanks.\n"},{"id":"548473","messageId":"20260717063559.1633567-1-cat@malon.dev","threadId":"65718","inReplyTo":"20260716084941.1101918-1-cat@malon.dev","subject":"[PATCH v7 0/4] environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-17T06:35:55Z","receivedAt":"2026-07-17T06:36:20Z","isPatch":true,"body":"This series moves 'trust_executable_bit' and 'has_symlinks' into\n'struct repo_config_values' to tie them to the specific repository\ninstance they were read from. Eager parsing is maintained because\nthese two flags are heavily consulted in hot paths.\n\nNote: 'repo_config_values()' still does not support any struct\nrepository other than the_repository due to how deeply these flags\nare accessed. In other words, this series of patches is laying\nthe groundwork for the eventual elimination of the_repository.\n\nPrevious related work:\n\n[PATCH 2/6] config: add trust_executable_bit to global config [1]\n[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]\n(This previous attempt was unsuccessful because the target location\nselected was 'struct repo_settings', which our analysis indicated\nwas not the optimal choice. For further details, please see: [3])\n\n[PATCH 5/6] config: move has_symlinks [4]\n\nRFC:\n\n - Is the locations of the newly introduced definitions/macros\n appropriate?\n\nChanges since V6:\n\n - In commit 2/4, keep ce_mode_from_stat() static inline. Teach it to\n take a 'struct repository *' parameter instead.\n\n - In commit 4/4, reword the commit message.\n\nThanks!\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\n[4] https://lore.kernel.org/git/a154008619790f7a60f2bba91db7b0fe29e67e1a.1685716420.git.gitgitgadget@gmail.com/\n[5] https://lore.kernel.org/git/xmqq7bokebct.fsf@gitster.g/\n\nTian Yuchen (4):\n  read-cache: remove redundant extern declarations\n  read-cache: pass 'repo' to 'ce_mode_from_stat()'\n  environment: move trust_executable_bit into repo_config_values\n  environment: move has_symlinks into repo_config_values\n\n apply.c                |  6 +++---\n builtin/update-index.c |  2 +-\n combine-diff.c         |  2 +-\n compat/mingw.c         | 17 +++++++++++++----\n compat/mingw.h         |  3 +++\n diff-lib.c             | 10 +++++-----\n entry.c                |  2 +-\n environment.c          | 27 +++++++++++++++++++++++----\n environment.h          |  8 ++++++--\n git-compat-util.h      |  4 ++++\n read-cache.c           | 15 +++++++--------\n read-cache.h           | 16 ++++++++++++----\n 12 files changed, 79 insertions(+), 33 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"548474","messageId":"20260717063559.1633567-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260717063559.1633567-1-cat@malon.dev","subject":"[PATCH v7 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-17T06:35:57Z","receivedAt":"2026-07-17T06:36:21Z","isPatch":true,"body":"The ce_mode_from_stat() function is a performance-critical static\ninline helper in 'read-cache.h'. As we migrate configuration\nvariables into the repository struct, this helper needs access\nto the repository context.\n\nUpdate the signature of ce_mode_from_stat() to take a 'struct\nrepository *' parameter, and update all callers to pass the\nappropriate repository instance.\n\nTo prepare for the overhead of replacing cheap global variable\naccesses with getter functions, the boolean expressions are\nreordered to evaluate 'S_ISREG(mode)' first.\n\nWhile at it, add a comment for ce_mode_from_stat().\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                |  2 +-\n builtin/update-index.c |  2 +-\n diff-lib.c             | 10 +++++-----\n read-cache.c           |  2 +-\n read-cache.h           | 15 ++++++++++++---\n 5 files changed, 20 insertions(+), 11 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..26286eb57b 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3894,7 +3894,7 @@ static int check_preimage(struct apply_state *state,\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n \t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n-\t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n+\t\t\tst_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\n \t\telse\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 8a5907767b..7917bd286f 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -293,7 +293,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len\n \tce->ce_flags = create_ce_flags(0);\n \tce->ce_namelen = len;\n \tfill_stat_cache_info(the_repository->index, ce, st);\n-\tce->ce_mode = ce_mode_from_stat(old, st->st_mode);\n+\tce->ce_mode = ce_mode_from_stat(the_repository, old, st->st_mode);\n \n \tif (index_path(the_repository->index, &ce->oid, path, st,\n \t\t       info_only ? 0 : INDEX_WRITE_OBJECT)) {\ndiff --git a/diff-lib.c b/diff-lib.c\nindex ae91027a02..46cae637ec 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -160,7 +160,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \n \t\t\tchanged = check_removed(ce, &st);\n \t\t\tif (!changed)\n-\t\t\t\twt_mode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\t\twt_mode = ce_mode_from_stat(revs->repo, ce, st.st_mode);\n \t\t\telse {\n \t\t\t\tif (changed < 0) {\n \t\t\t\t\tperror(ce->name);\n@@ -193,7 +193,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t\t\tnum_compare_stages++;\n \t\t\t\t\toidcpy(&dpath->parent[stage - 2].oid,\n \t\t\t\t\t       &nce->oid);\n-\t\t\t\t\tdpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);\n+\t\t\t\t\tdpath->parent[stage-2].mode = ce_mode_from_stat(revs->repo, nce, mode);\n \t\t\t\t\tdpath->parent[stage-2].status =\n \t\t\t\t\t\tDIFF_STATUS_MODIFIED;\n \t\t\t\t}\n@@ -262,7 +262,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t\tcontinue;\n \t\t\t} else if (revs->diffopt.ita_invisible_in_index &&\n \t\t\t\t   ce_intent_to_add(ce)) {\n-\t\t\t\tnewmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\t\tnewmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);\n \t\t\t\tdiff_addremove(&revs->diffopt, '+', newmode,\n \t\t\t\t\t       null_oid(the_hash_algo), 0, ce->name, 0);\n \t\t\t\tcontinue;\n@@ -270,7 +270,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \n \t\t\tchanged = match_stat_with_submodule(&revs->diffopt, ce, &st,\n \t\t\t\t\t\t\t    ce_option, &dirty_submodule);\n-\t\t\tnewmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\tnewmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);\n \t\t}\n \n \t\tif (!changed && !dirty_submodule) {\n@@ -338,7 +338,7 @@ static int get_stat_data(const struct cache_entry *ce,\n \t\tchanged = match_stat_with_submodule(diffopt, ce, &st,\n \t\t\t\t\t\t    0, dirty_submodule);\n \t\tif (changed) {\n-\t\t\tmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\tmode = ce_mode_from_stat(diffopt->repo, ce, st.st_mode);\n \t\t\toid = null_oid(the_hash_algo);\n \t\t}\n \t}\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..b37bf688ec 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -749,7 +749,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tint pos = index_name_pos_also_unmerged(istate, path, namelen);\n \n \t\tent = (0 <= pos) ? istate->cache[pos] : NULL;\n-\t\tce->ce_mode = ce_mode_from_stat(ent, st_mode);\n+\t\tce->ce_mode = ce_mode_from_stat(istate->repo, ent, st_mode);\n \t}\n \n \t/* When core.ignorecase=true, determine if a directory of the same name but differing\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..94b8d3e547 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -4,15 +4,24 @@\n #include \"read-cache-ll.h\"\n #include \"object.h\"\n #include \"pathspec.h\"\n+#include \"environment.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+/*\n+ * Determine the appropriate index mode for a file based on its stat()\n+ * information and the existing cache entry (if any).\n+ *\n+ * This function handles degradation for filesystems that lack\n+ * symlink support or reliable executable bits.\n+ */\n+static inline unsigned int ce_mode_from_stat(struct repository *repo,\n+\t\t\t\t\t     const struct cache_entry *ce,\n \t\t\t\t\t     unsigned int mode)\n {\n \textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n+\tif (S_ISREG(mode) && !has_symlinks &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (S_ISREG(mode) && !trust_executable_bit) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n-- \n2.43.0\n\n"},{"id":"548475","messageId":"20260717063559.1633567-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260717063559.1633567-1-cat@malon.dev","subject":"[PATCH v7 1/4] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-17T06:35:56Z","receivedAt":"2026-07-17T06:36:23Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"548476","messageId":"20260717063559.1633567-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260717063559.1633567-1-cat@malon.dev","subject":"[PATCH v7 3/4] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-17T06:35:58Z","receivedAt":"2026-07-17T06:36:25Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configuration\ninto the repository-specific 'repo_config_values'\nstruct.\n\nTo ensure code readability, the getter function\n'repo_trust_executable_bit()' has been introduced.\nCallers access this configuration by passing in 'repo'\nwhen possible, and explicitly fall back to 'the_repository'\nthe rest of time.\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       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  4 +++-\n read-cache.c  |  6 +++---\n read-cache.h  |  4 ++--\n 5 files changed, 18 insertions(+), 9 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 26286eb57b..edb1502414 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..75069a884d 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -142,6 +141,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+int repo_trust_executable_bit(struct repository *repo)\n+{\n+\treturn repo->gitdir?\n+\t\trepo_config_values(repo)->trust_executable_bit :\n+\t\t1;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..72b59fd89c 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 \tint apply_sparse_checkout;\n+\tint trust_executable_bit;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -123,6 +124,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+int repo_trust_executable_bit(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -160,7 +163,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex b37bf688ec..1f8b5ed15f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -208,7 +208,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -318,7 +318,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (repo_trust_executable_bit(the_repository) &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -739,7 +739,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\ndiff --git a/read-cache.h b/read-cache.h\nindex 94b8d3e547..4b54cfc57c 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -17,11 +17,11 @@ static inline unsigned int ce_mode_from_stat(struct repository *repo,\n \t\t\t\t\t     const struct cache_entry *ce,\n \t\t\t\t\t     unsigned int mode)\n {\n-\textern int trust_executable_bit, has_symlinks;\n+\textern int has_symlinks;\n \tif (S_ISREG(mode) && !has_symlinks &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (S_ISREG(mode) && !trust_executable_bit) {\n+\tif (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n-- \n2.43.0\n\n"},{"id":"548477","messageId":"20260717063559.1633567-5-cat@malon.dev","threadId":"65718","inReplyTo":"20260717063559.1633567-1-cat@malon.dev","subject":"[PATCH v7 4/4] environment: move has_symlinks into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-17T06:35:59Z","receivedAt":"2026-07-17T06:36:28Z","isPatch":true,"body":"Move the global 'has_symlinks' configuration into the\nrepository-specific 'repo_config_values' struct.\n\nIntroduce 'repo_has_symlinks()' getter for readability.\nCallers access this configuration by passing in 'repo'\nwhen possible, and explicitly fall back to\n'the_repository' the rest of the time.\n\nIntroduce 'platform_has_symlinks()' macro to allow\nplatform specific-customization, primarily to help MinGW.\nPlatforms can override this in their respective headers.\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           |  2 +-\n combine-diff.c    |  2 +-\n compat/mingw.c    | 17 +++++++++++++----\n compat/mingw.h    |  3 +++\n entry.c           |  2 +-\n environment.c     | 16 ++++++++++++++--\n environment.h     |  4 +++-\n git-compat-util.h |  4 ++++\n read-cache.c      |  7 ++++---\n read-cache.h      |  3 +--\n 10 files changed, 45 insertions(+), 15 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex edb1502414..b748192ee2 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -4511,7 +4511,7 @@ static int try_create_file(struct apply_state *state, const char *path,\n \t\treturn !!mkdir(path, 0777);\n \t}\n \n-\tif (has_symlinks && S_ISLNK(mode))\n+\tif (repo_has_symlinks(state->repo) && S_ISLNK(mode))\n \t\t/* Although buf:size is counted string, it also is NUL\n \t\t * terminated.\n \t\t */\ndiff --git a/combine-diff.c b/combine-diff.c\nindex b799862068..80e5c46e9b 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -1078,7 +1078,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\t/* if symlinks don't work, assume symlink if all parents\n \t\t\t * are symlinks\n \t\t\t */\n-\t\t\tis_file = has_symlinks;\n+\t\t\tis_file = repo_has_symlinks(rev->repo);\n \t\t\tfor (i = 0; !is_file && i < num_parent; i++)\n \t\t\t\tis_file = !S_ISLNK(elem->parent[i].mode);\n \t\t\tif (!is_file)\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex aa7525f419..4781911929 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -7,6 +7,7 @@\n #include \"config.h\"\n #include \"dir.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"gettext.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n@@ -1043,7 +1044,7 @@ int mingw_chdir(const char *dirname)\n \tif (xutftowcs_path(wdirname, dirname) < 0)\n \t\treturn -1;\n \n-\tif (has_symlinks) {\n+\tif (repo_has_symlinks(the_repository)) {\n \t\tHANDLE hnd = CreateFileW(wdirname, 0,\n \t\t\t\tFILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,\n \t\t\t\tOPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);\n@@ -2903,7 +2904,7 @@ int symlink(const char *target, const char *link)\n \tint len;\n \n \t/* fail if symlinks are disabled or API is not supported (WinXP) */\n-\tif (!has_symlinks) {\n+\tif (!repo_has_symlinks(the_repository)) {\n \t\terrno = ENOSYS;\n \t\treturn -1;\n \t}\n@@ -3173,15 +3174,23 @@ static void setup_windows_environment(void)\n \t\tif (!tmp && (tmp = getenv(\"USERPROFILE\")))\n \t\t\tsetenv(\"HOME\", tmp, 1);\n \t}\n+}\n \n+int mingw_platform_has_symlinks(void)\n+{\n+\tstatic int has_symlinks = -1;\n \t/*\n \t * Change 'core.symlinks' default to false, unless native symlinks are\n \t * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can\n \t * run the test suite (which doesn't obey config files) with or without\n \t * symlink support.\n \t */\n-\tif (!(tmp = getenv(\"MSYS\")) || !strstr(tmp, \"winsymlinks:nativestrict\"))\n-\t\thas_symlinks = 0;\n+\tif (has_symlinks < 0) {\n+\t\tconst char *tmp = getenv(\"MSYS\");\n+\t\thas_symlinks = (tmp && strstr(tmp, \"winsymlinks:nativestrict\")) ? 1 : 0;\n+\t}\n+\n+\treturn has_symlinks;\n }\n \n static void get_current_user_sid(PSID *sid, HANDLE *linked_token)\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 444daedfa5..df02aeb632 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -208,6 +208,9 @@ void open_in_gdb(void);\n  */\n int err_win_to_posix(DWORD winerr);\n \n+int mingw_platform_has_symlinks(void);\n+#define platform_has_symlinks() mingw_platform_has_symlinks()\n+\n #ifndef NO_UNIX_SOCKETS\n int mingw_have_unix_sockets(void);\n #undef have_unix_sockets\ndiff --git a/entry.c b/entry.c\nindex 7817aee362..f2854b4cd8 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -321,7 +321,7 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca\n \t\t * We can't make a real symlink; write out a regular file entry\n \t\t * with the symlink destination as its contents.\n \t\t */\n-\t\tif (!has_symlinks || to_tempfile)\n+\t\tif (!repo_has_symlinks(state->istate ? state->istate->repo : NULL) || to_tempfile)\n \t\t\tgoto write_file_entry;\n \n \t\tret = symlink(new_blob, path);\ndiff --git a/environment.c b/environment.c\nindex 75069a884d..760689d6e7 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -43,7 +43,6 @@ static int zlib_compression_seen;\n \n int trust_ctime = 1;\n int check_stat = 1;\n-int has_symlinks = 1;\n int minimum_abbrev = 4, default_abbrev = -1;\n int ignore_case;\n int assume_unchanged;\n@@ -148,6 +147,17 @@ int repo_trust_executable_bit(struct repository *repo)\n \t\t1;\n }\n \n+int repo_has_symlinks(struct repository *repo)\n+{\n+\tif (!repo)\n+\t\trepo = the_repository;\n+\n+\tif (!repo->gitdir)\n+\t\treturn platform_has_symlinks();\n+\n+\treturn repo_config_values(repo)->has_symlinks;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -336,7 +346,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.symlinks\")) {\n-\t\thas_symlinks = git_config_bool(var, value);\n+\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\t\tcfg->has_symlinks = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n@@ -727,5 +738,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->trust_executable_bit = 1;\n+\tcfg->has_symlinks = platform_has_symlinks();\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 72b59fd89c..ef64a783b0 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tint apply_sparse_checkout;\n \tint trust_executable_bit;\n+\tint has_symlinks;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -126,6 +127,8 @@ int git_default_core_config(const char *var, const char *value,\n \n int repo_trust_executable_bit(struct repository *repo);\n \n+int repo_has_symlinks(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -165,7 +168,6 @@ extern char *git_work_tree_cfg;\n /* Environment bits from configuration mechanism */\n extern int trust_ctime;\n extern int check_stat;\n-extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 5024814bd4..333a5acf33 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c)\n #define is_dir_sep git_is_dir_sep\n #endif\n \n+#ifndef platform_has_symlinks\n+#define platform_has_symlinks() 1\n+#endif\n+\n #ifndef offset_1st_component\n static inline int git_offset_1st_component(const char *path)\n {\ndiff --git a/read-cache.c b/read-cache.c\nindex 1f8b5ed15f..c2c3c2e6cc 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -206,7 +206,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n-\t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n+\t\treturn repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n \t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n@@ -324,7 +324,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\tbreak;\n \tcase S_IFLNK:\n \t\tif (!S_ISLNK(st->st_mode) &&\n-\t\t    (has_symlinks || !S_ISREG(st->st_mode)))\n+\t\t    (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode)))\n \t\t\tchanged |= TYPE_CHANGED;\n \t\tbreak;\n \tcase S_IFGITLINK:\n@@ -739,7 +739,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) &&\n+\t    repo_has_symlinks(istate->repo)) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\ndiff --git a/read-cache.h b/read-cache.h\nindex 4b54cfc57c..ab9d40aa81 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -17,8 +17,7 @@ static inline unsigned int ce_mode_from_stat(struct repository *repo,\n \t\t\t\t\t     const struct cache_entry *ce,\n \t\t\t\t\t     unsigned int mode)\n {\n-\textern int has_symlinks;\n-\tif (S_ISREG(mode) && !has_symlinks &&\n+\tif (S_ISREG(mode) && !repo_has_symlinks(repo) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n \tif (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {\n-- \n2.43.0\n\n"},{"id":"548540","messageId":"xmqq8q7961xe.fsf@gitster.g","threadId":"65718","inReplyTo":"20260717063559.1633567-4-cat@malon.dev","subject":"Re: [PATCH v7 3/4] environment: move trust_executable_bit into repo_config_values","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-07-17T16:01:01Z","receivedAt":"2026-07-17T16:01:04Z","isPatch":true,"body":"Tian Yuchen <cat@malon.dev> writes:\n\n> diff --git a/environment.c b/environment.c\n> index fc3ed8bb1c..75069a884d 100644\n> --- a/environment.c\n> +++ b/environment.c\n> @@ -41,7 +41,6 @@\n>  static int pack_compression_seen;\n>  static int zlib_compression_seen;\n>  \n> -int trust_executable_bit = 1;\n>  int trust_ctime = 1;\n>  int check_stat = 1;\n>  int has_symlinks = 1;\n> @@ -142,6 +141,13 @@ int is_bare_repository(void)\n>  \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>  }\n>  \n> +int repo_trust_executable_bit(struct repository *repo)\n> +{\n> +\treturn repo->gitdir?\n> +\t\trepo_config_values(repo)->trust_executable_bit :\n> +\t\t1;\n> +}\n> +\n>  int have_git_dir(void)\n>  {\n>  \treturn startup_info->have_repository\n\nTwo comments.\n\n * Missing SP before '?'.  It may be easier to read if it is written\n   like this:\n\n\treturn repo->gitdir\n\t       ? repo_config_values(repo)->trust_executable_bit\n\t       : 1;\n\n   which more clearly highlights the ternary structure.  If you tilt\n   your head 90 degrees to the left, you can almost see the parse\n   tree of the expression.\n  \n * Does it make sense to protect against a NULL 'repo' case, as\n   repo_protect_ntfs() and repo_protect_hfs() helpers do?  Or is it\n   better to crash loudly with a segfault to let the developer know\n   they have a bug to fix?  I lean toward the latter myself, and if\n   we go that route, we should probably stop using 'repo &&\n   repo->gitdir' elsewhere, rather than sweeping the problem under\n   the rug with defensive checks.\n"},{"id":"548593","messageId":"alvNq8rXF/jofqUc@szeder.dev","threadId":"65718","inReplyTo":"20260717063559.1633567-3-cat@malon.dev","subject":"Re: [PATCH v7 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()'","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-07-18T19:02:03Z","receivedAt":"2026-07-18T19:02:11Z","isPatch":true,"body":"On Fri, Jul 17, 2026 at 02:35:57PM +0800, Tian Yuchen wrote:\n> The ce_mode_from_stat() function is a performance-critical static\n> inline helper in 'read-cache.h'. As we migrate configuration\n> variables into the repository struct, this helper needs access\n> to the repository context.\n> \n> Update the signature of ce_mode_from_stat() to take a 'struct\n> repository *' parameter, and update all callers to pass the\n> appropriate repository instance.\n> \n> To prepare for the overhead of replacing cheap global variable\n> accesses with getter functions, the boolean expressions are\n> reordered to evaluate 'S_ISREG(mode)' first.\n> \n> While at it, add a comment for ce_mode_from_stat().\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\n> diff --git a/read-cache.h b/read-cache.h\n> index 043da1f1aa..94b8d3e547 100644\n> --- a/read-cache.h\n> +++ b/read-cache.h\n> @@ -4,15 +4,24 @@\n>  #include \"read-cache-ll.h\"\n>  #include \"object.h\"\n>  #include \"pathspec.h\"\n> +#include \"environment.h\"\n>  \n> -static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n> +/*\n> + * Determine the appropriate index mode for a file based on its stat()\n> + * information and the existing cache entry (if any).\n> + *\n> + * This function handles degradation for filesystems that lack\n> + * symlink support or reliable executable bits.\n> + */\n> +static inline unsigned int ce_mode_from_stat(struct repository *repo,\n\nThis new parameter is not yet used in this function, which causes\ncompilation errors in all source files which include \"read-cache.h\"\nwhen trying to build this commit using DEVELOPER=1, e.g.:\n\n      CC pathspec.o\n  In file included from pathspec.c:11:\n  read-cache.h: In function ‘ce_mode_from_stat’:\n  read-cache.h:16:65: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n     16 | static inline unsigned int ce_mode_from_stat(struct repository *repo,\n        |                                              ~~~~~~~~~~~~~~~~~~~^~~~\n  cc1: all warnings being treated as errors\n  make: *** [Makefile:2921: pathspec.o] Error 1\n      CC preload-index.o\n  In file included from preload-index.c:16:\n  read-cache.h: In function ‘ce_mode_from_stat’:\n  read-cache.h:16:65: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n     16 | static inline unsigned int ce_mode_from_stat(struct repository *repo,\n        |                                              ~~~~~~~~~~~~~~~~~~~^~~~\n  cc1: all warnings being treated as errors\n  make: *** [Makefile:2921: preload-index.o] Error 1\n      CC read-cache.o\n  In file included from read-cache.c:34:\n  read-cache.h: In function ‘ce_mode_from_stat’:\n  read-cache.h:16:65: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n     16 | static inline unsigned int ce_mode_from_stat(struct repository *repo,\n        |                                              ~~~~~~~~~~~~~~~~~~~^~~~\n  cc1: all warnings being treated as errors\n  make: *** [Makefile:2921: read-cache.o] Error 1\n\nI think the new parameter should be marked as UNUSED in this patch,\nand then the UNUSED should be dropped in the next, where you start\nusing the parameter.\n\n> +\t\t\t\t\t     const struct cache_entry *ce,\n>  \t\t\t\t\t     unsigned int mode)\n>  {\n>  \textern int trust_executable_bit, has_symlinks;\n> -\tif (!has_symlinks && S_ISREG(mode) &&\n> +\tif (S_ISREG(mode) && !has_symlinks &&\n>  \t    ce && S_ISLNK(ce->ce_mode))\n>  \t\treturn ce->ce_mode;\n> -\tif (!trust_executable_bit && S_ISREG(mode)) {\n> +\tif (S_ISREG(mode) && !trust_executable_bit) {\n>  \t\tif (ce && S_ISREG(ce->ce_mode))\n>  \t\t\treturn ce->ce_mode;\n>  \t\treturn create_ce_mode(0666);\n> -- \n> 2.43.0\n> \n"},{"id":"548663","messageId":"8b9528b8-198b-489f-8f0e-fbd0c7d07b64@malon.dev","threadId":"65718","inReplyTo":"alvNq8rXF/jofqUc@szeder.dev","subject":"Re: [PATCH v7 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T09:13:17Z","receivedAt":"2026-07-20T09:13:30Z","isPatch":true,"body":"On 7/19/26 03:02, SZEDER Gábor wrote:\n> On Fri, Jul 17, 2026 at 02:35:57PM +0800, Tian Yuchen wrote:\n>> The ce_mode_from_stat() function is a performance-critical static\n>> inline helper in 'read-cache.h'. As we migrate configuration\n>> variables into the repository struct, this helper needs access\n>> to the repository context.\n>>\n>> Update the signature of ce_mode_from_stat() to take a 'struct\n>> repository *' parameter, and update all callers to pass the\n>> appropriate repository instance.\n>>\n>> To prepare for the overhead of replacing cheap global variable\n>> accesses with getter functions, the boolean expressions are\n>> reordered to evaluate 'S_ISREG(mode)' first.\n>>\n>> While at it, add a comment for ce_mode_from_stat().\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> \n>> diff --git a/read-cache.h b/read-cache.h\n>> index 043da1f1aa..94b8d3e547 100644\n>> --- a/read-cache.h\n>> +++ b/read-cache.h\n>> @@ -4,15 +4,24 @@\n>>   #include \"read-cache-ll.h\"\n>>   #include \"object.h\"\n>>   #include \"pathspec.h\"\n>> +#include \"environment.h\"\n>>   \n>> -static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n>> +/*\n>> + * Determine the appropriate index mode for a file based on its stat()\n>> + * information and the existing cache entry (if any).\n>> + *\n>> + * This function handles degradation for filesystems that lack\n>> + * symlink support or reliable executable bits.\n>> + */\n>> +static inline unsigned int ce_mode_from_stat(struct repository *repo,\n> \n> This new parameter is not yet used in this function, which causes\n> compilation errors in all source files which include \"read-cache.h\"\n> when trying to build this commit using DEVELOPER=1, e.g.:\n> \n>        CC pathspec.o\n>    In file included from pathspec.c:11:\n>    read-cache.h: In function ‘ce_mode_from_stat’:\n>    read-cache.h:16:65: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n>       16 | static inline unsigned int ce_mode_from_stat(struct repository *repo,\n>          |                                              ~~~~~~~~~~~~~~~~~~~^~~~\n>    cc1: all warnings being treated as errors\n>    make: *** [Makefile:2921: pathspec.o] Error 1\n>        CC preload-index.o\n>    In file included from preload-index.c:16:\n>    read-cache.h: In function ‘ce_mode_from_stat’:\n>    read-cache.h:16:65: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n>       16 | static inline unsigned int ce_mode_from_stat(struct repository *repo,\n>          |                                              ~~~~~~~~~~~~~~~~~~~^~~~\n>    cc1: all warnings being treated as errors\n>    make: *** [Makefile:2921: preload-index.o] Error 1\n>        CC read-cache.o\n>    In file included from read-cache.c:34:\n>    read-cache.h: In function ‘ce_mode_from_stat’:\n>    read-cache.h:16:65: error: unused parameter ‘repo’ [-Werror=unused-parameter]\n>       16 | static inline unsigned int ce_mode_from_stat(struct repository *repo,\n>          |                                              ~~~~~~~~~~~~~~~~~~~^~~~\n>    cc1: all warnings being treated as errors\n>    make: *** [Makefile:2921: read-cache.o] Error 1\n> \n\nNice catch.\n\n> I think the new parameter should be marked as UNUSED in this patch,\n> and then the UNUSED should be dropped in the next, where you start\n> using the parameter.\n> \n>> +\t\t\t\t\t     const struct cache_entry *ce,\n>>   \t\t\t\t\t     unsigned int mode)\n>>   {\n>>   \textern int trust_executable_bit, has_symlinks;\n>> -\tif (!has_symlinks && S_ISREG(mode) &&\n>> +\tif (S_ISREG(mode) && !has_symlinks &&\n>>   \t    ce && S_ISLNK(ce->ce_mode))\n>>   \t\treturn ce->ce_mode;\n>> -\tif (!trust_executable_bit && S_ISREG(mode)) {\n>> +\tif (S_ISREG(mode) && !trust_executable_bit) {\n>>   \t\tif (ce && S_ISREG(ce->ce_mode))\n>>   \t\t\treturn ce->ce_mode;\n>>   \t\treturn create_ce_mode(0666);\n>> -- \n>> 2.43.0\n>>\n\nBut 'USUSED' cannot be used here since the corresponding header \n(git-compat-util.h, or more specifically compat/posix.h) is not included.\n\nCan we write..\n\n\t(void)repo; /* TODO: use this parameter in the next patch */\n\n..to keep it simple?\n\n\nRegards, yuchen\n"},{"id":"548665","messageId":"al3v0NVZJYS9SVZF@szeder.dev","threadId":"65718","inReplyTo":"8b9528b8-198b-489f-8f0e-fbd0c7d07b64@malon.dev","subject":"Re: [PATCH v7 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()'","fromName":"SZEDER Gábor","fromEmail":"szeder.dev@gmail.com","sentAt":"2026-07-20T09:52:16Z","receivedAt":"2026-07-20T09:52:20Z","isPatch":true,"body":"On Mon, Jul 20, 2026 at 05:13:17PM +0800, Tian Yuchen wrote:\n> On 7/19/26 03:02, SZEDER Gábor wrote:\n> > On Fri, Jul 17, 2026 at 02:35:57PM +0800, Tian Yuchen wrote:\n> > > diff --git a/read-cache.h b/read-cache.h\n> > > index 043da1f1aa..94b8d3e547 100644\n> > > --- a/read-cache.h\n> > > +++ b/read-cache.h\n> > > @@ -4,15 +4,24 @@\n> > >   #include \"read-cache-ll.h\"\n> > >   #include \"object.h\"\n> > >   #include \"pathspec.h\"\n> > > +#include \"environment.h\"\n> > > -static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n> > > +/*\n> > > + * Determine the appropriate index mode for a file based on its stat()\n> > > + * information and the existing cache entry (if any).\n> > > + *\n> > > + * This function handles degradation for filesystems that lack\n> > > + * symlink support or reliable executable bits.\n> > > + */\n> > > +static inline unsigned int ce_mode_from_stat(struct repository *repo,\n> > \n> > This new parameter is not yet used in this function, which causes\n> > compilation errors in all source files which include \"read-cache.h\"\n> > when trying to build this commit using DEVELOPER=1, e.g.:\n\n> > I think the new parameter should be marked as UNUSED in this patch,\n> > and then the UNUSED should be dropped in the next, where you start\n> > using the parameter.\n> > \n> > > +\t\t\t\t\t     const struct cache_entry *ce,\n> > >   \t\t\t\t\t     unsigned int mode)\n> > >   {\n> > >   \textern int trust_executable_bit, has_symlinks;\n> > > -\tif (!has_symlinks && S_ISREG(mode) &&\n> > > +\tif (S_ISREG(mode) && !has_symlinks &&\n> > >   \t    ce && S_ISLNK(ce->ce_mode))\n> > >   \t\treturn ce->ce_mode;\n> > > -\tif (!trust_executable_bit && S_ISREG(mode)) {\n> > > +\tif (S_ISREG(mode) && !trust_executable_bit) {\n> > >   \t\tif (ce && S_ISREG(ce->ce_mode))\n> > >   \t\t\treturn ce->ce_mode;\n> > >   \t\treturn create_ce_mode(0666);\n> > > -- \n> > > 2.43.0\n> > > \n> \n> But 'USUSED' cannot be used here since the corresponding header\n> (git-compat-util.h, or more specifically compat/posix.h) is not included.\n\nUNUSED _can_ be used here, because:\n\n  - This is a header file, so it's not supposed to be compiled on its\n    own.\n  - All C source files including this header file must start with\n    including \"git-compat-util.h\", so by the time they include\n    \"read-cache.h\", the UNUSED macro is already defined.\n\n"},{"id":"548669","messageId":"3cd5e386-aaa4-4a96-92e1-1ed244d39c9e@malon.dev","threadId":"65718","inReplyTo":"xmqq8q7961xe.fsf@gitster.g","subject":"Re: [PATCH v7 3/4] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T10:09:51Z","receivedAt":"2026-07-20T10:10:03Z","isPatch":true,"body":"On 7/18/26 00:01, Junio C Hamano wrote:\n> Tian Yuchen <cat@malon.dev> writes:\n> \n>> diff --git a/environment.c b/environment.c\n>> index fc3ed8bb1c..75069a884d 100644\n>> --- a/environment.c\n>> +++ b/environment.c\n>> @@ -41,7 +41,6 @@\n>>   static int pack_compression_seen;\n>>   static int zlib_compression_seen;\n>>   \n>> -int trust_executable_bit = 1;\n>>   int trust_ctime = 1;\n>>   int check_stat = 1;\n>>   int has_symlinks = 1;\n>> @@ -142,6 +141,13 @@ int is_bare_repository(void)\n>>   \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n>>   }\n>>   \n>> +int repo_trust_executable_bit(struct repository *repo)\n>> +{\n>> +\treturn repo->gitdir?\n>> +\t\trepo_config_values(repo)->trust_executable_bit :\n>> +\t\t1;\n>> +}\n>> +\n>>   int have_git_dir(void)\n>>   {\n>>   \treturn startup_info->have_repository\n> \n> Two comments.\n> \n>   * Missing SP before '?'.  It may be easier to read if it is written\n>     like this:\n> \n> \treturn repo->gitdir\n> \t       ? repo_config_values(repo)->trust_executable_bit\n> \t       : 1;\n> \n>     which more clearly highlights the ternary structure.  If you tilt\n>     your head 90 degrees to the left, you can almost see the parse\n>     tree of the expression.\n>    \n\nOkay.\n\n>   * Does it make sense to protect against a NULL 'repo' case, as\n>     repo_protect_ntfs() and repo_protect_hfs() helpers do?  Or is it\n>     better to crash loudly with a segfault to let the developer know\n>     they have a bug to fix?  I lean toward the latter myself, and if\n>     we go that route, we should probably stop using 'repo &&\n>     repo->gitdir' elsewhere, rather than sweeping the problem under\n>     the rug with defensive checks.\n\nSounds sensible. I will drop the checks and adjust the calls themselves \ninstead.\n\n\nNote that repo_protect_ntfs() looks like this:\n\n  int repo_protect_ntfs(struct repository *repo)\n  {\n\treturn (repo && repo->initialized) ?\n  \t\trepo_config_values(repo)->protect_ntfs :\n  \t\tPROTECT_NTFS_DEFAULT;\n  }\n\nDo we need another fixup commit for it?\n\nRegards, yuchen\n\n"},{"id":"548670","messageId":"6407614a-156b-40e1-bf70-e54ce84427ad@malon.dev","threadId":"65718","inReplyTo":"al3v0NVZJYS9SVZF@szeder.dev","subject":"Re: [PATCH v7 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T10:12:30Z","receivedAt":"2026-07-20T10:12:41Z","isPatch":true,"body":"On 7/20/26 17:52, SZEDER Gábor wrote:\n> On Mon, Jul 20, 2026 at 05:13:17PM +0800, Tian Yuchen wrote:\n>> On 7/19/26 03:02, SZEDER Gábor wrote:\n>>> On Fri, Jul 17, 2026 at 02:35:57PM +0800, Tian Yuchen wrote:\n>>>> diff --git a/read-cache.h b/read-cache.h\n>>>> index 043da1f1aa..94b8d3e547 100644\n>>>> --- a/read-cache.h\n>>>> +++ b/read-cache.h\n>>>> @@ -4,15 +4,24 @@\n>>>>    #include \"read-cache-ll.h\"\n>>>>    #include \"object.h\"\n>>>>    #include \"pathspec.h\"\n>>>> +#include \"environment.h\"\n>>>> -static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n>>>> +/*\n>>>> + * Determine the appropriate index mode for a file based on its stat()\n>>>> + * information and the existing cache entry (if any).\n>>>> + *\n>>>> + * This function handles degradation for filesystems that lack\n>>>> + * symlink support or reliable executable bits.\n>>>> + */\n>>>> +static inline unsigned int ce_mode_from_stat(struct repository *repo,\n>>>\n>>> This new parameter is not yet used in this function, which causes\n>>> compilation errors in all source files which include \"read-cache.h\"\n>>> when trying to build this commit using DEVELOPER=1, e.g.:\n> \n>>> I think the new parameter should be marked as UNUSED in this patch,\n>>> and then the UNUSED should be dropped in the next, where you start\n>>> using the parameter.\n>>>\n>>>> +\t\t\t\t\t     const struct cache_entry *ce,\n>>>>    \t\t\t\t\t     unsigned int mode)\n>>>>    {\n>>>>    \textern int trust_executable_bit, has_symlinks;\n>>>> -\tif (!has_symlinks && S_ISREG(mode) &&\n>>>> +\tif (S_ISREG(mode) && !has_symlinks &&\n>>>>    \t    ce && S_ISLNK(ce->ce_mode))\n>>>>    \t\treturn ce->ce_mode;\n>>>> -\tif (!trust_executable_bit && S_ISREG(mode)) {\n>>>> +\tif (S_ISREG(mode) && !trust_executable_bit) {\n>>>>    \t\tif (ce && S_ISREG(ce->ce_mode))\n>>>>    \t\t\treturn ce->ce_mode;\n>>>>    \t\treturn create_ce_mode(0666);\n>>>> -- \n>>>> 2.43.0\n>>>>\n>>\n>> But 'USUSED' cannot be used here since the corresponding header\n>> (git-compat-util.h, or more specifically compat/posix.h) is not included.\n> \n> UNUSED _can_ be used here, because:\n> \n>    - This is a header file, so it's not supposed to be compiled on its\n>      own.\n>    - All C source files including this header file must start with\n>      including \"git-compat-util.h\", so by the time they include\n>      \"read-cache.h\", the UNUSED macro is already defined.\n> \n\nI see.\n\nThanks, yuchen\n"},{"id":"548672","messageId":"20260720105335.3202013-1-cat@malon.dev","threadId":"65718","inReplyTo":"20260717063559.1633567-1-cat@malon.dev","subject":"[PATCH v8 0/4] environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T10:53:31Z","receivedAt":"2026-07-20T10:53:48Z","isPatch":true,"body":"This series moves 'trust_executable_bit' and 'has_symlinks' into\n'struct repo_config_values' to tie them to the specific repository\ninstance they were read from. Eager parsing is maintained because\nthese two flags are heavily consulted in hot paths.\n\nNote: 'repo_config_values()' still does not support any struct\nrepository other than the_repository due to how deeply these flags\nare accessed. In other words, this series of patches is laying\nthe groundwork for the eventual elimination of the_repository.\n\nPrevious related work:\n\n[PATCH 2/6] config: add trust_executable_bit to global config [1]\n[PATCH] Refactor 'trust_executable_bit' to repository-scoped setting [2]\n(This previous attempt was unsuccessful because the target location\nselected was 'struct repo_settings', which our analysis indicated\nwas not the optimal choice. For further details, please see: [3])\n\n[PATCH 5/6] config: move has_symlinks [4]\n\nRFC:\n\n - Is the locations of the newly introduced definitions/macros\n appropriate?\n\nChanges since V7:\n\n - In commit 2/4, mark the 'struct repository' parameter of\n ce_mode_from_stat() UNUSED. In commit 3/4, drop it.\n\n - Don't check '!repo' in the getters, which lets the developers know\n there is a bug to fix when NULL is passed in. Callers should be\n responsible of passing non-null repos. Therefore, adjust the call in\n write_entry().\n\n Change back to check 'repo->initiaized' instead of 'repo->gitdir'.\n\nThanks!\n\n[1] https://lore.kernel.org/git/837b5360b40f992351f489a0ae05fedf49884c6e.1685716420.git.gitgitgadget@gmail.com/\n[2] https://lore.kernel.org/git/20260301190017.53539-1-dronarajgyawali@gmail.com/\n[3] https://lore.kernel.org/git/xmqq1pht6nyx.fsf@gitster.g/\n[4] https://lore.kernel.org/git/a154008619790f7a60f2bba91db7b0fe29e67e1a.1685716420.git.gitgitgadget@gmail.com/\n[5] https://lore.kernel.org/git/xmqq7bokebct.fsf@gitster.g/\n\nTian Yuchen (4):\n  read-cache: remove redundant extern declarations\n  read-cache: pass 'repo' to 'ce_mode_from_stat()'\n  environment: move trust_executable_bit into repo_config_values\n  environment: move has_symlinks into repo_config_values\n\n apply.c                |  6 +++---\n builtin/update-index.c |  2 +-\n combine-diff.c         |  2 +-\n compat/mingw.c         | 17 +++++++++++++----\n compat/mingw.h         |  3 +++\n diff-lib.c             | 10 +++++-----\n entry.c                |  3 ++-\n environment.c          | 23 +++++++++++++++++++----\n environment.h          |  8 ++++++--\n git-compat-util.h      |  4 ++++\n read-cache.c           | 15 +++++++--------\n read-cache.h           | 16 ++++++++++++----\n 12 files changed, 76 insertions(+), 33 deletions(-)\n\n-- \n2.43.0\n\n"},{"id":"548673","messageId":"20260720105335.3202013-2-cat@malon.dev","threadId":"65718","inReplyTo":"20260720105335.3202013-1-cat@malon.dev","subject":"[PATCH v8 1/4] read-cache: remove redundant extern declarations","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T10:53:32Z","receivedAt":"2026-07-20T10:53:51Z","isPatch":true,"body":"The 'read-cache.c' file already includes 'environment.h', which provides\nthe extern declarations for variables like 'trust_executable_bit' and\n'has_symlinks'.\n\nRemove the redundant extern declarations inside 'st_mode_from_ce()' to\nclean up the code.\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 read-cache.c | 2 --\n 1 file changed, 2 deletions(-)\n\ndiff --git a/read-cache.c b/read-cache.c\nindex 38a04b8de3..c44e4d128f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -204,8 +204,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st\n \n static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n-\textern int trust_executable_bit, has_symlinks;\n-\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n-- \n2.43.0\n\n"},{"id":"548674","messageId":"20260720105335.3202013-3-cat@malon.dev","threadId":"65718","inReplyTo":"20260720105335.3202013-1-cat@malon.dev","subject":"[PATCH v8 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()'","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T10:53:33Z","receivedAt":"2026-07-20T10:53:56Z","isPatch":true,"body":"The ce_mode_from_stat() function is a performance-critical static\ninline helper in 'read-cache.h'. As we migrate configuration\nvariables into the repository struct, this helper needs access\nto the repository context.\n\nUpdate the signature of ce_mode_from_stat() to take a 'struct\nrepository *' parameter, and update all callers to pass the\nappropriate repository instance.\n\nTo prepare for the overhead of replacing cheap global variable\naccesses with getter functions, the boolean expressions are\nreordered to evaluate 'S_ISREG(mode)' first.\n\nWhile at it, add a comment for ce_mode_from_stat().\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                |  2 +-\n builtin/update-index.c |  2 +-\n diff-lib.c             | 10 +++++-----\n read-cache.c           |  2 +-\n read-cache.h           | 15 ++++++++++++---\n 5 files changed, 20 insertions(+), 11 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 249248d4f2..26286eb57b 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3894,7 +3894,7 @@ static int check_preimage(struct apply_state *state,\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n \t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n-\t\t\tst_mode = ce_mode_from_stat(*ce, st->st_mode);\n+\t\t\tst_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\n \t\telse\ndiff --git a/builtin/update-index.c b/builtin/update-index.c\nindex 8a5907767b..7917bd286f 100644\n--- a/builtin/update-index.c\n+++ b/builtin/update-index.c\n@@ -293,7 +293,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len\n \tce->ce_flags = create_ce_flags(0);\n \tce->ce_namelen = len;\n \tfill_stat_cache_info(the_repository->index, ce, st);\n-\tce->ce_mode = ce_mode_from_stat(old, st->st_mode);\n+\tce->ce_mode = ce_mode_from_stat(the_repository, old, st->st_mode);\n \n \tif (index_path(the_repository->index, &ce->oid, path, st,\n \t\t       info_only ? 0 : INDEX_WRITE_OBJECT)) {\ndiff --git a/diff-lib.c b/diff-lib.c\nindex ae91027a02..46cae637ec 100644\n--- a/diff-lib.c\n+++ b/diff-lib.c\n@@ -160,7 +160,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \n \t\t\tchanged = check_removed(ce, &st);\n \t\t\tif (!changed)\n-\t\t\t\twt_mode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\t\twt_mode = ce_mode_from_stat(revs->repo, ce, st.st_mode);\n \t\t\telse {\n \t\t\t\tif (changed < 0) {\n \t\t\t\t\tperror(ce->name);\n@@ -193,7 +193,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t\t\tnum_compare_stages++;\n \t\t\t\t\toidcpy(&dpath->parent[stage - 2].oid,\n \t\t\t\t\t       &nce->oid);\n-\t\t\t\t\tdpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode);\n+\t\t\t\t\tdpath->parent[stage-2].mode = ce_mode_from_stat(revs->repo, nce, mode);\n \t\t\t\t\tdpath->parent[stage-2].status =\n \t\t\t\t\t\tDIFF_STATUS_MODIFIED;\n \t\t\t\t}\n@@ -262,7 +262,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \t\t\t\tcontinue;\n \t\t\t} else if (revs->diffopt.ita_invisible_in_index &&\n \t\t\t\t   ce_intent_to_add(ce)) {\n-\t\t\t\tnewmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\t\tnewmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);\n \t\t\t\tdiff_addremove(&revs->diffopt, '+', newmode,\n \t\t\t\t\t       null_oid(the_hash_algo), 0, ce->name, 0);\n \t\t\t\tcontinue;\n@@ -270,7 +270,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option)\n \n \t\t\tchanged = match_stat_with_submodule(&revs->diffopt, ce, &st,\n \t\t\t\t\t\t\t    ce_option, &dirty_submodule);\n-\t\t\tnewmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\tnewmode = ce_mode_from_stat(revs->repo, ce, st.st_mode);\n \t\t}\n \n \t\tif (!changed && !dirty_submodule) {\n@@ -338,7 +338,7 @@ static int get_stat_data(const struct cache_entry *ce,\n \t\tchanged = match_stat_with_submodule(diffopt, ce, &st,\n \t\t\t\t\t\t    0, dirty_submodule);\n \t\tif (changed) {\n-\t\t\tmode = ce_mode_from_stat(ce, st.st_mode);\n+\t\t\tmode = ce_mode_from_stat(diffopt->repo, ce, st.st_mode);\n \t\t\toid = null_oid(the_hash_algo);\n \t\t}\n \t}\ndiff --git a/read-cache.c b/read-cache.c\nindex c44e4d128f..b37bf688ec 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -749,7 +749,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tint pos = index_name_pos_also_unmerged(istate, path, namelen);\n \n \t\tent = (0 <= pos) ? istate->cache[pos] : NULL;\n-\t\tce->ce_mode = ce_mode_from_stat(ent, st_mode);\n+\t\tce->ce_mode = ce_mode_from_stat(istate->repo, ent, st_mode);\n \t}\n \n \t/* When core.ignorecase=true, determine if a directory of the same name but differing\ndiff --git a/read-cache.h b/read-cache.h\nindex 043da1f1aa..af8c657ecb 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -4,15 +4,24 @@\n #include \"read-cache-ll.h\"\n #include \"object.h\"\n #include \"pathspec.h\"\n+#include \"environment.h\"\n \n-static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce,\n+/*\n+ * Determine the appropriate index mode for a file based on its stat()\n+ * information and the existing cache entry (if any).\n+ *\n+ * This function handles degradation for filesystems that lack\n+ * symlink support or reliable executable bits.\n+ */\n+static inline unsigned int ce_mode_from_stat(struct repository *repo UNUSED,\n+\t\t\t\t\t     const struct cache_entry *ce,\n \t\t\t\t\t     unsigned int mode)\n {\n \textern int trust_executable_bit, has_symlinks;\n-\tif (!has_symlinks && S_ISREG(mode) &&\n+\tif (S_ISREG(mode) && !has_symlinks &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (!trust_executable_bit && S_ISREG(mode)) {\n+\tif (S_ISREG(mode) && !trust_executable_bit) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n-- \n2.43.0\n\n"},{"id":"548675","messageId":"20260720105335.3202013-4-cat@malon.dev","threadId":"65718","inReplyTo":"20260720105335.3202013-1-cat@malon.dev","subject":"[PATCH v8 3/4] environment: move trust_executable_bit into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T10:53:34Z","receivedAt":"2026-07-20T10:53:58Z","isPatch":true,"body":"Move the global 'trust_executable_bit' configuration\ninto the repository-specific 'repo_config_values'\nstruct.\n\nTo ensure code readability, the getter function\n'repo_trust_executable_bit()' has been introduced.\nCallers access this configuration by passing in 'repo'\nwhen possible, and explicitly fall back to 'the_repository'\nthe rest of time.\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       |  2 +-\n environment.c | 11 +++++++++--\n environment.h |  4 +++-\n read-cache.c  |  6 +++---\n read-cache.h  |  6 +++---\n 5 files changed, 19 insertions(+), 10 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex 26286eb57b..edb1502414 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state,\n \t\tif (*ce && !(*ce)->ce_mode)\n \t\t\tBUG(\"ce_mode == 0 for path '%s'\", old_name);\n \n-\t\tif (trust_executable_bit || !S_ISREG(st->st_mode))\n+\t\tif (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode))\n \t\t\tst_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode);\n \t\telse if (*ce)\n \t\t\tst_mode = (*ce)->ce_mode;\ndiff --git a/environment.c b/environment.c\nindex fc3ed8bb1c..32b110c405 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -41,7 +41,6 @@\n static int pack_compression_seen;\n static int zlib_compression_seen;\n \n-int trust_executable_bit = 1;\n int trust_ctime = 1;\n int check_stat = 1;\n int has_symlinks = 1;\n@@ -142,6 +141,13 @@ int is_bare_repository(void)\n \treturn is_bare_repository_cfg && !repo_get_work_tree(the_repository);\n }\n \n+int repo_trust_executable_bit(struct repository *repo)\n+{\n+\treturn repo->initialized\n+\t\t? repo_config_values(repo)->trust_executable_bit\n+\t\t: 1;\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value,\n \n \t/* This needs a better name */\n \tif (!strcmp(var, \"core.filemode\")) {\n-\t\ttrust_executable_bit = git_config_bool(var, value);\n+\t\tcfg->trust_executable_bit = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"core.trustctime\")) {\n@@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n {\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n+\tcfg->trust_executable_bit = 1;\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 123a71cdc8..72b59fd89c 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 \tint apply_sparse_checkout;\n+\tint trust_executable_bit;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -123,6 +124,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+int repo_trust_executable_bit(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -160,7 +163,6 @@ int is_bare_repository(void);\n extern char *git_work_tree_cfg;\n \n /* Environment bits from configuration mechanism */\n-extern int trust_executable_bit;\n extern int trust_ctime;\n extern int check_stat;\n extern int has_symlinks;\ndiff --git a/read-cache.c b/read-cache.c\nindex b37bf688ec..1f8b5ed15f 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -208,7 +208,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n \tcase S_IFLNK:\n \t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n-\t\treturn (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG;\n+\t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n \t\treturn S_IFDIR | 0755;\n \tcase S_IFDIR:\n@@ -318,7 +318,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\t/* We consider only the owner x bit to be relevant for\n \t\t * \"mode changes\"\n \t\t */\n-\t\tif (trust_executable_bit &&\n+\t\tif (repo_trust_executable_bit(the_repository) &&\n \t\t    (0100 & (ce->ce_mode ^ st->st_mode)))\n \t\t\tchanged |= MODE_CHANGED;\n \t\tbreak;\n@@ -739,7 +739,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (trust_executable_bit && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\ndiff --git a/read-cache.h b/read-cache.h\nindex af8c657ecb..4b54cfc57c 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -13,15 +13,15 @@\n  * This function handles degradation for filesystems that lack\n  * symlink support or reliable executable bits.\n  */\n-static inline unsigned int ce_mode_from_stat(struct repository *repo UNUSED,\n+static inline unsigned int ce_mode_from_stat(struct repository *repo,\n \t\t\t\t\t     const struct cache_entry *ce,\n \t\t\t\t\t     unsigned int mode)\n {\n-\textern int trust_executable_bit, has_symlinks;\n+\textern int has_symlinks;\n \tif (S_ISREG(mode) && !has_symlinks &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n-\tif (S_ISREG(mode) && !trust_executable_bit) {\n+\tif (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {\n \t\tif (ce && S_ISREG(ce->ce_mode))\n \t\t\treturn ce->ce_mode;\n \t\treturn create_ce_mode(0666);\n-- \n2.43.0\n\n"},{"id":"548676","messageId":"20260720105335.3202013-5-cat@malon.dev","threadId":"65718","inReplyTo":"20260720105335.3202013-1-cat@malon.dev","subject":"[PATCH v8 4/4] environment: move has_symlinks into repo_config_values","fromName":"Tian Yuchen","fromEmail":"cat@malon.dev","sentAt":"2026-07-20T10:53:35Z","receivedAt":"2026-07-20T10:54:01Z","isPatch":true,"body":"Move the global 'has_symlinks' configuration into the\nrepository-specific 'repo_config_values' struct.\n\nIntroduce 'repo_has_symlinks()' getter for readability.\nCallers access this configuration by passing in 'repo'\nwhen possible, and explicitly fall back to\n'the_repository' the rest of the time.\n\nIntroduce 'platform_has_symlinks()' macro to allow\nplatform specific-customization, primarily to help MinGW.\nPlatforms can override this in their respective headers.\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           |  2 +-\n combine-diff.c    |  2 +-\n compat/mingw.c    | 17 +++++++++++++----\n compat/mingw.h    |  3 +++\n entry.c           |  3 ++-\n environment.c     | 12 ++++++++++--\n environment.h     |  4 +++-\n git-compat-util.h |  4 ++++\n read-cache.c      |  7 ++++---\n read-cache.h      |  3 +--\n 10 files changed, 42 insertions(+), 15 deletions(-)\n\ndiff --git a/apply.c b/apply.c\nindex edb1502414..b748192ee2 100644\n--- a/apply.c\n+++ b/apply.c\n@@ -4511,7 +4511,7 @@ static int try_create_file(struct apply_state *state, const char *path,\n \t\treturn !!mkdir(path, 0777);\n \t}\n \n-\tif (has_symlinks && S_ISLNK(mode))\n+\tif (repo_has_symlinks(state->repo) && S_ISLNK(mode))\n \t\t/* Although buf:size is counted string, it also is NUL\n \t\t * terminated.\n \t\t */\ndiff --git a/combine-diff.c b/combine-diff.c\nindex b799862068..80e5c46e9b 100644\n--- a/combine-diff.c\n+++ b/combine-diff.c\n@@ -1078,7 +1078,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,\n \t\t\t/* if symlinks don't work, assume symlink if all parents\n \t\t\t * are symlinks\n \t\t\t */\n-\t\t\tis_file = has_symlinks;\n+\t\t\tis_file = repo_has_symlinks(rev->repo);\n \t\t\tfor (i = 0; !is_file && i < num_parent; i++)\n \t\t\t\tis_file = !S_ISLNK(elem->parent[i].mode);\n \t\t\tif (!is_file)\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex aa7525f419..4781911929 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -7,6 +7,7 @@\n #include \"config.h\"\n #include \"dir.h\"\n #include \"environment.h\"\n+#include \"repository.h\"\n #include \"gettext.h\"\n #include \"run-command.h\"\n #include \"strbuf.h\"\n@@ -1043,7 +1044,7 @@ int mingw_chdir(const char *dirname)\n \tif (xutftowcs_path(wdirname, dirname) < 0)\n \t\treturn -1;\n \n-\tif (has_symlinks) {\n+\tif (repo_has_symlinks(the_repository)) {\n \t\tHANDLE hnd = CreateFileW(wdirname, 0,\n \t\t\t\tFILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,\n \t\t\t\tOPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);\n@@ -2903,7 +2904,7 @@ int symlink(const char *target, const char *link)\n \tint len;\n \n \t/* fail if symlinks are disabled or API is not supported (WinXP) */\n-\tif (!has_symlinks) {\n+\tif (!repo_has_symlinks(the_repository)) {\n \t\terrno = ENOSYS;\n \t\treturn -1;\n \t}\n@@ -3173,15 +3174,23 @@ static void setup_windows_environment(void)\n \t\tif (!tmp && (tmp = getenv(\"USERPROFILE\")))\n \t\t\tsetenv(\"HOME\", tmp, 1);\n \t}\n+}\n \n+int mingw_platform_has_symlinks(void)\n+{\n+\tstatic int has_symlinks = -1;\n \t/*\n \t * Change 'core.symlinks' default to false, unless native symlinks are\n \t * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can\n \t * run the test suite (which doesn't obey config files) with or without\n \t * symlink support.\n \t */\n-\tif (!(tmp = getenv(\"MSYS\")) || !strstr(tmp, \"winsymlinks:nativestrict\"))\n-\t\thas_symlinks = 0;\n+\tif (has_symlinks < 0) {\n+\t\tconst char *tmp = getenv(\"MSYS\");\n+\t\thas_symlinks = (tmp && strstr(tmp, \"winsymlinks:nativestrict\")) ? 1 : 0;\n+\t}\n+\n+\treturn has_symlinks;\n }\n \n static void get_current_user_sid(PSID *sid, HANDLE *linked_token)\ndiff --git a/compat/mingw.h b/compat/mingw.h\nindex 444daedfa5..df02aeb632 100644\n--- a/compat/mingw.h\n+++ b/compat/mingw.h\n@@ -208,6 +208,9 @@ void open_in_gdb(void);\n  */\n int err_win_to_posix(DWORD winerr);\n \n+int mingw_platform_has_symlinks(void);\n+#define platform_has_symlinks() mingw_platform_has_symlinks()\n+\n #ifndef NO_UNIX_SOCKETS\n int mingw_have_unix_sockets(void);\n #undef have_unix_sockets\ndiff --git a/entry.c b/entry.c\nindex 7817aee362..5913a8b51f 100644\n--- a/entry.c\n+++ b/entry.c\n@@ -321,7 +321,8 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca\n \t\t * We can't make a real symlink; write out a regular file entry\n \t\t * with the symlink destination as its contents.\n \t\t */\n-\t\tif (!has_symlinks || to_tempfile)\n+\t\tif (!repo_has_symlinks(state->istate && state->istate->repo ?\n+\t\t\t\t       state->istate->repo : the_repository) || to_tempfile)\n \t\t\tgoto write_file_entry;\n \n \t\tret = symlink(new_blob, path);\ndiff --git a/environment.c b/environment.c\nindex 32b110c405..e351043446 100644\n--- a/environment.c\n+++ b/environment.c\n@@ -43,7 +43,6 @@ static int zlib_compression_seen;\n \n int trust_ctime = 1;\n int check_stat = 1;\n-int has_symlinks = 1;\n int minimum_abbrev = 4, default_abbrev = -1;\n int ignore_case;\n int assume_unchanged;\n@@ -148,6 +147,13 @@ int repo_trust_executable_bit(struct repository *repo)\n \t\t: 1;\n }\n \n+int repo_has_symlinks(struct repository *repo)\n+{\n+\treturn repo->initialized\n+\t\t? repo_config_values(repo)->has_symlinks\n+\t\t: platform_has_symlinks();\n+}\n+\n int have_git_dir(void)\n {\n \treturn startup_info->have_repository\n@@ -336,7 +342,8 @@ int git_default_core_config(const char *var, const char *value,\n \t}\n \n \tif (!strcmp(var, \"core.symlinks\")) {\n-\t\thas_symlinks = git_config_bool(var, value);\n+\t\tstruct repo_config_values *cfg = repo_config_values(the_repository);\n+\t\tcfg->has_symlinks = git_config_bool(var, value);\n \t\treturn 0;\n \t}\n \n@@ -727,5 +734,6 @@ void repo_config_values_init(struct repo_config_values *cfg)\n \tcfg->attributes_file = NULL;\n \tcfg->apply_sparse_checkout = 0;\n \tcfg->trust_executable_bit = 1;\n+\tcfg->has_symlinks = platform_has_symlinks();\n \tcfg->branch_track = BRANCH_TRACK_REMOTE;\n }\ndiff --git a/environment.h b/environment.h\nindex 72b59fd89c..ef64a783b0 100644\n--- a/environment.h\n+++ b/environment.h\n@@ -92,6 +92,7 @@ struct repo_config_values {\n \tchar *attributes_file;\n \tint apply_sparse_checkout;\n \tint trust_executable_bit;\n+\tint has_symlinks;\n \n \t/* section \"branch\" config values */\n \tenum branch_track branch_track;\n@@ -126,6 +127,8 @@ int git_default_core_config(const char *var, const char *value,\n \n int repo_trust_executable_bit(struct repository *repo);\n \n+int repo_has_symlinks(struct repository *repo);\n+\n void repo_config_values_init(struct repo_config_values *cfg);\n \n /*\n@@ -165,7 +168,6 @@ extern char *git_work_tree_cfg;\n /* Environment bits from configuration mechanism */\n extern int trust_ctime;\n extern int check_stat;\n-extern int has_symlinks;\n extern int minimum_abbrev, default_abbrev;\n extern int ignore_case;\n extern int assume_unchanged;\ndiff --git a/git-compat-util.h b/git-compat-util.h\nindex 5024814bd4..333a5acf33 100644\n--- a/git-compat-util.h\n+++ b/git-compat-util.h\n@@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c)\n #define is_dir_sep git_is_dir_sep\n #endif\n \n+#ifndef platform_has_symlinks\n+#define platform_has_symlinks() 1\n+#endif\n+\n #ifndef offset_1st_component\n static inline int git_offset_1st_component(const char *path)\n {\ndiff --git a/read-cache.c b/read-cache.c\nindex 1f8b5ed15f..c2c3c2e6cc 100644\n--- a/read-cache.c\n+++ b/read-cache.c\n@@ -206,7 +206,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)\n {\n \tswitch (ce->ce_mode & S_IFMT) {\n \tcase S_IFLNK:\n-\t\treturn has_symlinks ? S_IFLNK : (S_IFREG | 0644);\n+\t\treturn repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644);\n \tcase S_IFREG:\n \t\treturn (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;\n \tcase S_IFGITLINK:\n@@ -324,7 +324,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)\n \t\tbreak;\n \tcase S_IFLNK:\n \t\tif (!S_ISLNK(st->st_mode) &&\n-\t\t    (has_symlinks || !S_ISREG(st->st_mode)))\n+\t\t    (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode)))\n \t\t\tchanged |= TYPE_CHANGED;\n \t\tbreak;\n \tcase S_IFGITLINK:\n@@ -739,7 +739,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,\n \t\tce->ce_flags |= CE_INTENT_TO_ADD;\n \n \n-\tif (repo_trust_executable_bit(istate->repo) && has_symlinks) {\n+\tif (repo_trust_executable_bit(istate->repo) &&\n+\t    repo_has_symlinks(istate->repo)) {\n \t\tce->ce_mode = create_ce_mode(st_mode);\n \t} else {\n \t\t/* If there is an existing entry, pick the mode bits and type\ndiff --git a/read-cache.h b/read-cache.h\nindex 4b54cfc57c..ab9d40aa81 100644\n--- a/read-cache.h\n+++ b/read-cache.h\n@@ -17,8 +17,7 @@ static inline unsigned int ce_mode_from_stat(struct repository *repo,\n \t\t\t\t\t     const struct cache_entry *ce,\n \t\t\t\t\t     unsigned int mode)\n {\n-\textern int has_symlinks;\n-\tif (S_ISREG(mode) && !has_symlinks &&\n+\tif (S_ISREG(mode) && !repo_has_symlinks(repo) &&\n \t    ce && S_ISLNK(ce->ce_mode))\n \t\treturn ce->ce_mode;\n \tif (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {\n-- \n2.43.0\n\n"}]}