From 8817d7931aab41d4423fbf588f1ab2247070d816 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Mon, 20 Jul 2026 18:53:32 +0800 Subject: [PATCH 1/4] read-cache: remove redundant extern declarations The 'read-cache.c' file already includes 'environment.h', which provides the extern declarations for variables like 'trust_executable_bit' and 'has_symlinks'. Remove the redundant extern declarations inside 'st_mode_from_ce()' to clean up the code. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- read-cache.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/read-cache.c b/read-cache.c index 21829102ae..1ced8c630e 100644 --- a/read-cache.c +++ b/read-cache.c @@ -205,8 +205,6 @@ void fill_stat_cache_info(struct index_state *istate, struct cache_entry *ce, st static unsigned int st_mode_from_ce(const struct cache_entry *ce) { - extern int trust_executable_bit, has_symlinks; - switch (ce->ce_mode & S_IFMT) { case S_IFLNK: return has_symlinks ? S_IFLNK : (S_IFREG | 0644); From b7b6e9e02f6b6b84848b2458d5d2b3d47260d4bb Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Mon, 20 Jul 2026 18:53:33 +0800 Subject: [PATCH 2/4] read-cache: pass 'repo' to 'ce_mode_from_stat()' The ce_mode_from_stat() function is a performance-critical static inline helper in 'read-cache.h'. As we migrate configuration variables into the repository struct, this helper needs access to the repository context. Update the signature of ce_mode_from_stat() to take a 'struct repository *' parameter, and update all callers to pass the appropriate repository instance. To prepare for the overhead of replacing cheap global variable accesses with getter functions, the boolean expressions are reordered to evaluate 'S_ISREG(mode)' first. While at it, add a comment for ce_mode_from_stat(). Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- apply.c | 2 +- builtin/update-index.c | 2 +- diff-lib.c | 10 +++++----- read-cache.c | 2 +- read-cache.h | 15 ++++++++++++--- 5 files changed, 20 insertions(+), 11 deletions(-) diff --git a/apply.c b/apply.c index 249248d4f2..26286eb57b 100644 --- a/apply.c +++ b/apply.c @@ -3894,7 +3894,7 @@ static int check_preimage(struct apply_state *state, BUG("ce_mode == 0 for path '%s'", old_name); if (trust_executable_bit || !S_ISREG(st->st_mode)) - st_mode = ce_mode_from_stat(*ce, st->st_mode); + st_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode); else if (*ce) st_mode = (*ce)->ce_mode; else diff --git a/builtin/update-index.c b/builtin/update-index.c index 3d6646c318..afd672a914 100644 --- a/builtin/update-index.c +++ b/builtin/update-index.c @@ -294,7 +294,7 @@ static int add_one_path(const struct cache_entry *old, const char *path, int len ce->ce_flags = create_ce_flags(0); ce->ce_namelen = len; fill_stat_cache_info(the_repository->index, ce, st); - ce->ce_mode = ce_mode_from_stat(old, st->st_mode); + ce->ce_mode = ce_mode_from_stat(the_repository, old, st->st_mode); if (index_path(the_repository->index, &ce->oid, path, st, info_only ? 0 : INDEX_WRITE_OBJECT)) { diff --git a/diff-lib.c b/diff-lib.c index ae91027a02..46cae637ec 100644 --- a/diff-lib.c +++ b/diff-lib.c @@ -160,7 +160,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) changed = check_removed(ce, &st); if (!changed) - wt_mode = ce_mode_from_stat(ce, st.st_mode); + wt_mode = ce_mode_from_stat(revs->repo, ce, st.st_mode); else { if (changed < 0) { perror(ce->name); @@ -193,7 +193,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) num_compare_stages++; oidcpy(&dpath->parent[stage - 2].oid, &nce->oid); - dpath->parent[stage-2].mode = ce_mode_from_stat(nce, mode); + dpath->parent[stage-2].mode = ce_mode_from_stat(revs->repo, nce, mode); dpath->parent[stage-2].status = DIFF_STATUS_MODIFIED; } @@ -262,7 +262,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) continue; } else if (revs->diffopt.ita_invisible_in_index && ce_intent_to_add(ce)) { - newmode = ce_mode_from_stat(ce, st.st_mode); + newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode); diff_addremove(&revs->diffopt, '+', newmode, null_oid(the_hash_algo), 0, ce->name, 0); continue; @@ -270,7 +270,7 @@ void run_diff_files(struct rev_info *revs, unsigned int option) changed = match_stat_with_submodule(&revs->diffopt, ce, &st, ce_option, &dirty_submodule); - newmode = ce_mode_from_stat(ce, st.st_mode); + newmode = ce_mode_from_stat(revs->repo, ce, st.st_mode); } if (!changed && !dirty_submodule) { @@ -338,7 +338,7 @@ static int get_stat_data(const struct cache_entry *ce, changed = match_stat_with_submodule(diffopt, ce, &st, 0, dirty_submodule); if (changed) { - mode = ce_mode_from_stat(ce, st.st_mode); + mode = ce_mode_from_stat(diffopt->repo, ce, st.st_mode); oid = null_oid(the_hash_algo); } } diff --git a/read-cache.c b/read-cache.c index 1ced8c630e..a9f0591743 100644 --- a/read-cache.c +++ b/read-cache.c @@ -750,7 +750,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, int pos = index_name_pos_also_unmerged(istate, path, namelen); ent = (0 <= pos) ? istate->cache[pos] : NULL; - ce->ce_mode = ce_mode_from_stat(ent, st_mode); + ce->ce_mode = ce_mode_from_stat(istate->repo, ent, st_mode); } /* When core.ignorecase=true, determine if a directory of the same name but differing diff --git a/read-cache.h b/read-cache.h index 043da1f1aa..af8c657ecb 100644 --- a/read-cache.h +++ b/read-cache.h @@ -4,15 +4,24 @@ #include "read-cache-ll.h" #include "object.h" #include "pathspec.h" +#include "environment.h" -static inline unsigned int ce_mode_from_stat(const struct cache_entry *ce, +/* + * Determine the appropriate index mode for a file based on its stat() + * information and the existing cache entry (if any). + * + * This function handles degradation for filesystems that lack + * symlink support or reliable executable bits. + */ +static inline unsigned int ce_mode_from_stat(struct repository *repo UNUSED, + const struct cache_entry *ce, unsigned int mode) { extern int trust_executable_bit, has_symlinks; - if (!has_symlinks && S_ISREG(mode) && + if (S_ISREG(mode) && !has_symlinks && ce && S_ISLNK(ce->ce_mode)) return ce->ce_mode; - if (!trust_executable_bit && S_ISREG(mode)) { + if (S_ISREG(mode) && !trust_executable_bit) { if (ce && S_ISREG(ce->ce_mode)) return ce->ce_mode; return create_ce_mode(0666); From 99c79dabb09a298863cd96398305d2957e60d022 Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Mon, 20 Jul 2026 18:53:34 +0800 Subject: [PATCH 3/4] environment: move trust_executable_bit into repo_config_values Move the global 'trust_executable_bit' configuration into the repository-specific 'repo_config_values' struct. To ensure code readability, the getter function 'repo_trust_executable_bit()' has been introduced. Callers access this configuration by passing in 'repo' when possible, and explicitly fall back to 'the_repository' the rest of time. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- apply.c | 2 +- environment.c | 11 +++++++++-- environment.h | 4 +++- read-cache.c | 6 +++--- read-cache.h | 6 +++--- 5 files changed, 19 insertions(+), 10 deletions(-) diff --git a/apply.c b/apply.c index 26286eb57b..edb1502414 100644 --- a/apply.c +++ b/apply.c @@ -3893,7 +3893,7 @@ static int check_preimage(struct apply_state *state, if (*ce && !(*ce)->ce_mode) BUG("ce_mode == 0 for path '%s'", old_name); - if (trust_executable_bit || !S_ISREG(st->st_mode)) + if (repo_trust_executable_bit(state->repo) || !S_ISREG(st->st_mode)) st_mode = ce_mode_from_stat(state->repo, *ce, st->st_mode); else if (*ce) st_mode = (*ce)->ce_mode; diff --git a/environment.c b/environment.c index fc3ed8bb1c..32b110c405 100644 --- a/environment.c +++ b/environment.c @@ -41,7 +41,6 @@ static int pack_compression_seen; static int zlib_compression_seen; -int trust_executable_bit = 1; int trust_ctime = 1; int check_stat = 1; int has_symlinks = 1; @@ -142,6 +141,13 @@ int is_bare_repository(void) return is_bare_repository_cfg && !repo_get_work_tree(the_repository); } +int repo_trust_executable_bit(struct repository *repo) +{ + return repo->initialized + ? repo_config_values(repo)->trust_executable_bit + : 1; +} + int have_git_dir(void) { return startup_info->have_repository @@ -305,7 +311,7 @@ int git_default_core_config(const char *var, const char *value, /* This needs a better name */ if (!strcmp(var, "core.filemode")) { - trust_executable_bit = git_config_bool(var, value); + cfg->trust_executable_bit = git_config_bool(var, value); return 0; } if (!strcmp(var, "core.trustctime")) { @@ -720,5 +726,6 @@ void repo_config_values_init(struct repo_config_values *cfg) { cfg->attributes_file = NULL; cfg->apply_sparse_checkout = 0; + cfg->trust_executable_bit = 1; cfg->branch_track = BRANCH_TRACK_REMOTE; } diff --git a/environment.h b/environment.h index 9eb97b3869..c15456fc0d 100644 --- a/environment.h +++ b/environment.h @@ -91,6 +91,7 @@ struct repo_config_values { /* section "core" config values */ char *attributes_file; int apply_sparse_checkout; + int trust_executable_bit; /* section "branch" config values */ enum branch_track branch_track; @@ -123,6 +124,8 @@ int git_default_config(const char *, const char *, int git_default_core_config(const char *var, const char *value, const struct config_context *ctx, void *cb); +int repo_trust_executable_bit(struct repository *repo); + void repo_config_values_init(struct repo_config_values *cfg); /* @@ -158,7 +161,6 @@ int is_bare_repository(void); extern char *git_work_tree_cfg; /* Environment bits from configuration mechanism */ -extern int trust_executable_bit; extern int trust_ctime; extern int check_stat; extern int has_symlinks; diff --git a/read-cache.c b/read-cache.c index a9f0591743..90789ff049 100644 --- a/read-cache.c +++ b/read-cache.c @@ -209,7 +209,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce) case S_IFLNK: return has_symlinks ? S_IFLNK : (S_IFREG | 0644); case S_IFREG: - return (ce->ce_mode & (trust_executable_bit ? 0755 : 0644)) | S_IFREG; + return (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG; case S_IFGITLINK: return S_IFDIR | 0755; case S_IFDIR: @@ -319,7 +319,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st) /* We consider only the owner x bit to be relevant for * "mode changes" */ - if (trust_executable_bit && + if (repo_trust_executable_bit(the_repository) && (0100 & (ce->ce_mode ^ st->st_mode))) changed |= MODE_CHANGED; break; @@ -740,7 +740,7 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, ce->ce_flags |= CE_INTENT_TO_ADD; - if (trust_executable_bit && has_symlinks) { + if (repo_trust_executable_bit(istate->repo) && has_symlinks) { ce->ce_mode = create_ce_mode(st_mode); } else { /* If there is an existing entry, pick the mode bits and type diff --git a/read-cache.h b/read-cache.h index af8c657ecb..4b54cfc57c 100644 --- a/read-cache.h +++ b/read-cache.h @@ -13,15 +13,15 @@ * This function handles degradation for filesystems that lack * symlink support or reliable executable bits. */ -static inline unsigned int ce_mode_from_stat(struct repository *repo UNUSED, +static inline unsigned int ce_mode_from_stat(struct repository *repo, const struct cache_entry *ce, unsigned int mode) { - extern int trust_executable_bit, has_symlinks; + extern int has_symlinks; if (S_ISREG(mode) && !has_symlinks && ce && S_ISLNK(ce->ce_mode)) return ce->ce_mode; - if (S_ISREG(mode) && !trust_executable_bit) { + if (S_ISREG(mode) && !repo_trust_executable_bit(repo)) { if (ce && S_ISREG(ce->ce_mode)) return ce->ce_mode; return create_ce_mode(0666); From df2bc04e6937ba35b9a905a65dd0137627b46c8b Mon Sep 17 00:00:00 2001 From: Tian Yuchen Date: Mon, 20 Jul 2026 18:53:35 +0800 Subject: [PATCH 4/4] environment: move has_symlinks into repo_config_values Move the global 'has_symlinks' configuration into the repository-specific 'repo_config_values' struct. Introduce 'repo_has_symlinks()' getter for readability. Callers access this configuration by passing in 'repo' when possible, and explicitly fall back to 'the_repository' the rest of the time. Introduce 'platform_has_symlinks()' macro to allow platform specific-customization, primarily to help MinGW. Platforms can override this in their respective headers. Mentored-by: Christian Couder Mentored-by: Ayush Chandekar Mentored-by: Olamide Caleb Bello Signed-off-by: Tian Yuchen Signed-off-by: Junio C Hamano --- apply.c | 2 +- combine-diff.c | 2 +- compat/mingw.c | 17 +++++++++++++---- compat/mingw.h | 3 +++ entry.c | 3 ++- environment.c | 12 ++++++++++-- environment.h | 4 +++- git-compat-util.h | 4 ++++ read-cache.c | 7 ++++--- read-cache.h | 3 +-- 10 files changed, 42 insertions(+), 15 deletions(-) diff --git a/apply.c b/apply.c index edb1502414..b748192ee2 100644 --- a/apply.c +++ b/apply.c @@ -4511,7 +4511,7 @@ static int try_create_file(struct apply_state *state, const char *path, return !!mkdir(path, 0777); } - if (has_symlinks && S_ISLNK(mode)) + if (repo_has_symlinks(state->repo) && S_ISLNK(mode)) /* Although buf:size is counted string, it also is NUL * terminated. */ diff --git a/combine-diff.c b/combine-diff.c index b799862068..80e5c46e9b 100644 --- a/combine-diff.c +++ b/combine-diff.c @@ -1078,7 +1078,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent, /* if symlinks don't work, assume symlink if all parents * are symlinks */ - is_file = has_symlinks; + is_file = repo_has_symlinks(rev->repo); for (i = 0; !is_file && i < num_parent; i++) is_file = !S_ISLNK(elem->parent[i].mode); if (!is_file) diff --git a/compat/mingw.c b/compat/mingw.c index aa7525f419..4781911929 100644 --- a/compat/mingw.c +++ b/compat/mingw.c @@ -7,6 +7,7 @@ #include "config.h" #include "dir.h" #include "environment.h" +#include "repository.h" #include "gettext.h" #include "run-command.h" #include "strbuf.h" @@ -1043,7 +1044,7 @@ int mingw_chdir(const char *dirname) if (xutftowcs_path(wdirname, dirname) < 0) return -1; - if (has_symlinks) { + if (repo_has_symlinks(the_repository)) { HANDLE hnd = CreateFileW(wdirname, 0, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); @@ -2903,7 +2904,7 @@ int symlink(const char *target, const char *link) int len; /* fail if symlinks are disabled or API is not supported (WinXP) */ - if (!has_symlinks) { + if (!repo_has_symlinks(the_repository)) { errno = ENOSYS; return -1; } @@ -3173,15 +3174,23 @@ static void setup_windows_environment(void) if (!tmp && (tmp = getenv("USERPROFILE"))) setenv("HOME", tmp, 1); } +} +int mingw_platform_has_symlinks(void) +{ + static int has_symlinks = -1; /* * Change 'core.symlinks' default to false, unless native symlinks are * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can * run the test suite (which doesn't obey config files) with or without * symlink support. */ - if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict")) - has_symlinks = 0; + if (has_symlinks < 0) { + const char *tmp = getenv("MSYS"); + has_symlinks = (tmp && strstr(tmp, "winsymlinks:nativestrict")) ? 1 : 0; + } + + return has_symlinks; } static void get_current_user_sid(PSID *sid, HANDLE *linked_token) diff --git a/compat/mingw.h b/compat/mingw.h index 444daedfa5..df02aeb632 100644 --- a/compat/mingw.h +++ b/compat/mingw.h @@ -208,6 +208,9 @@ void open_in_gdb(void); */ int err_win_to_posix(DWORD winerr); +int mingw_platform_has_symlinks(void); +#define platform_has_symlinks() mingw_platform_has_symlinks() + #ifndef NO_UNIX_SOCKETS int mingw_have_unix_sockets(void); #undef have_unix_sockets diff --git a/entry.c b/entry.c index 7817aee362..5913a8b51f 100644 --- a/entry.c +++ b/entry.c @@ -321,7 +321,8 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca * We can't make a real symlink; write out a regular file entry * with the symlink destination as its contents. */ - if (!has_symlinks || to_tempfile) + if (!repo_has_symlinks(state->istate && state->istate->repo ? + state->istate->repo : the_repository) || to_tempfile) goto write_file_entry; ret = symlink(new_blob, path); diff --git a/environment.c b/environment.c index 32b110c405..e351043446 100644 --- a/environment.c +++ b/environment.c @@ -43,7 +43,6 @@ static int zlib_compression_seen; int trust_ctime = 1; int check_stat = 1; -int has_symlinks = 1; int minimum_abbrev = 4, default_abbrev = -1; int ignore_case; int assume_unchanged; @@ -148,6 +147,13 @@ int repo_trust_executable_bit(struct repository *repo) : 1; } +int repo_has_symlinks(struct repository *repo) +{ + return repo->initialized + ? repo_config_values(repo)->has_symlinks + : platform_has_symlinks(); +} + int have_git_dir(void) { return startup_info->have_repository @@ -336,7 +342,8 @@ int git_default_core_config(const char *var, const char *value, } if (!strcmp(var, "core.symlinks")) { - has_symlinks = git_config_bool(var, value); + struct repo_config_values *cfg = repo_config_values(the_repository); + cfg->has_symlinks = git_config_bool(var, value); return 0; } @@ -727,5 +734,6 @@ void repo_config_values_init(struct repo_config_values *cfg) cfg->attributes_file = NULL; cfg->apply_sparse_checkout = 0; cfg->trust_executable_bit = 1; + cfg->has_symlinks = platform_has_symlinks(); cfg->branch_track = BRANCH_TRACK_REMOTE; } diff --git a/environment.h b/environment.h index c15456fc0d..8f54c481e9 100644 --- a/environment.h +++ b/environment.h @@ -92,6 +92,7 @@ struct repo_config_values { char *attributes_file; int apply_sparse_checkout; int trust_executable_bit; + int has_symlinks; /* section "branch" config values */ enum branch_track branch_track; @@ -126,6 +127,8 @@ int git_default_core_config(const char *var, const char *value, int repo_trust_executable_bit(struct repository *repo); +int repo_has_symlinks(struct repository *repo); + void repo_config_values_init(struct repo_config_values *cfg); /* @@ -163,7 +166,6 @@ extern char *git_work_tree_cfg; /* Environment bits from configuration mechanism */ extern int trust_ctime; extern int check_stat; -extern int has_symlinks; extern int minimum_abbrev, default_abbrev; extern int ignore_case; extern int assume_unchanged; diff --git a/git-compat-util.h b/git-compat-util.h index 8809776407..a0f901ce79 100644 --- a/git-compat-util.h +++ b/git-compat-util.h @@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c) #define is_dir_sep git_is_dir_sep #endif +#ifndef platform_has_symlinks +#define platform_has_symlinks() 1 +#endif + #ifndef offset_1st_component static inline int git_offset_1st_component(const char *path) { diff --git a/read-cache.c b/read-cache.c index 90789ff049..046b1e649e 100644 --- a/read-cache.c +++ b/read-cache.c @@ -207,7 +207,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce) { switch (ce->ce_mode & S_IFMT) { case S_IFLNK: - return has_symlinks ? S_IFLNK : (S_IFREG | 0644); + return repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644); case S_IFREG: return (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG; case S_IFGITLINK: @@ -325,7 +325,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st) break; case S_IFLNK: if (!S_ISLNK(st->st_mode) && - (has_symlinks || !S_ISREG(st->st_mode))) + (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode))) changed |= TYPE_CHANGED; break; case S_IFGITLINK: @@ -740,7 +740,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st, ce->ce_flags |= CE_INTENT_TO_ADD; - if (repo_trust_executable_bit(istate->repo) && has_symlinks) { + if (repo_trust_executable_bit(istate->repo) && + repo_has_symlinks(istate->repo)) { ce->ce_mode = create_ce_mode(st_mode); } else { /* If there is an existing entry, pick the mode bits and type diff --git a/read-cache.h b/read-cache.h index 4b54cfc57c..ab9d40aa81 100644 --- a/read-cache.h +++ b/read-cache.h @@ -17,8 +17,7 @@ static inline unsigned int ce_mode_from_stat(struct repository *repo, const struct cache_entry *ce, unsigned int mode) { - extern int has_symlinks; - if (S_ISREG(mode) && !has_symlinks && + if (S_ISREG(mode) && !repo_has_symlinks(repo) && ce && S_ISLNK(ce->ce_mode)) return ce->ce_mode; if (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {