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 <christian.couder@gmail.com> Mentored-by: Ayush Chandekar <ayu.chandekar@gmail.com> Mentored-by: Olamide Caleb Bello <belkid98@gmail.com> Signed-off-by: Tian Yuchen <cat@malon.dev> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Tian Yuchen committed Jul 20, 2026 at 18:53 UTC df2bc04e6937ba35b9a905a65dd0137627b46c8b
10 files changed +42 -15
apply.c
+1 -1
@@ -4511,7 +4511,7 @@ static int try_create_file(struct apply_state *state, const char *path,
4511 return !!mkdir(path, 0777);
4512 }
4513
4514 - if (has_symlinks && S_ISLNK(mode))
4514 + if (repo_has_symlinks(state->repo) && S_ISLNK(mode))
4515 /* Although buf:size is counted string, it also is NUL
4516 * terminated.
4517 */
combine-diff.c
+1 -1
@@ -1078,7 +1078,7 @@ static void show_patch_diff(struct combine_diff_path *elem, int num_parent,
1078 /* if symlinks don't work, assume symlink if all parents
1079 * are symlinks
1080 */
1081 - is_file = has_symlinks;
1081 + is_file = repo_has_symlinks(rev->repo);
1082 for (i = 0; !is_file && i < num_parent; i++)
1083 is_file = !S_ISLNK(elem->parent[i].mode);
1084 if (!is_file)
compat/mingw.c
+13 -4
@@ -7,6 +7,7 @@
7 #include "config.h"
8 #include "dir.h"
9 #include "environment.h"
10 +#include "repository.h"
11 #include "gettext.h"
12 #include "run-command.h"
13 #include "strbuf.h"
@@ -1043,7 +1044,7 @@ int mingw_chdir(const char *dirname)
1044 if (xutftowcs_path(wdirname, dirname) < 0)
1045 return -1;
1046
1046 - if (has_symlinks) {
1047 + if (repo_has_symlinks(the_repository)) {
1048 HANDLE hnd = CreateFileW(wdirname, 0,
1049 FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL,
1050 OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
@@ -2903,7 +2904,7 @@ int symlink(const char *target, const char *link)
2904 int len;
2905
2906 /* fail if symlinks are disabled or API is not supported (WinXP) */
2906 - if (!has_symlinks) {
2907 + if (!repo_has_symlinks(the_repository)) {
2908 errno = ENOSYS;
2909 return -1;
2910 }
@@ -3173,15 +3174,23 @@ static void setup_windows_environment(void)
3174 if (!tmp && (tmp = getenv("USERPROFILE")))
3175 setenv("HOME", tmp, 1);
3176 }
3177 +}
3178
3179 +int mingw_platform_has_symlinks(void)
3180 +{
3181 + static int has_symlinks = -1;
3182 /*
3183 * Change 'core.symlinks' default to false, unless native symlinks are
3184 * enabled in MSys2 (via 'MSYS=winsymlinks:nativestrict'). Thus we can
3185 * run the test suite (which doesn't obey config files) with or without
3186 * symlink support.
3187 */
3183 - if (!(tmp = getenv("MSYS")) || !strstr(tmp, "winsymlinks:nativestrict"))
3184 - has_symlinks = 0;
3188 + if (has_symlinks < 0) {
3189 + const char *tmp = getenv("MSYS");
3190 + has_symlinks = (tmp && strstr(tmp, "winsymlinks:nativestrict")) ? 1 : 0;
3191 + }
3192 +
3193 + return has_symlinks;
3194 }
3195
3196 static void get_current_user_sid(PSID *sid, HANDLE *linked_token)
compat/mingw.h
+3
@@ -208,6 +208,9 @@ void open_in_gdb(void);
208 */
209 int err_win_to_posix(DWORD winerr);
210
211 +int mingw_platform_has_symlinks(void);
212 +#define platform_has_symlinks() mingw_platform_has_symlinks()
213 +
214 #ifndef NO_UNIX_SOCKETS
215 int mingw_have_unix_sockets(void);
216 #undef have_unix_sockets
entry.c
+2 -1
@@ -321,7 +321,8 @@ static int write_entry(struct cache_entry *ce, char *path, struct conv_attrs *ca
321 * We can't make a real symlink; write out a regular file entry
322 * with the symlink destination as its contents.
323 */
324 - if (!has_symlinks || to_tempfile)
324 + if (!repo_has_symlinks(state->istate && state->istate->repo ?
325 + state->istate->repo : the_repository) || to_tempfile)
326 goto write_file_entry;
327
328 ret = symlink(new_blob, path);
environment.c
+10 -2
@@ -43,7 +43,6 @@ static int zlib_compression_seen;
43
44 int trust_ctime = 1;
45 int check_stat = 1;
46 -int has_symlinks = 1;
46 int minimum_abbrev = 4, default_abbrev = -1;
47 int ignore_case;
48 int assume_unchanged;
@@ -148,6 +147,13 @@ int repo_trust_executable_bit(struct repository *repo)
147 : 1;
148 }
149
150 +int repo_has_symlinks(struct repository *repo)
151 +{
152 + return repo->initialized
153 + ? repo_config_values(repo)->has_symlinks
154 + : platform_has_symlinks();
155 +}
156 +
157 int have_git_dir(void)
158 {
159 return startup_info->have_repository
@@ -336,7 +342,8 @@ int git_default_core_config(const char *var, const char *value,
342 }
343
344 if (!strcmp(var, "core.symlinks")) {
339 - has_symlinks = git_config_bool(var, value);
345 + struct repo_config_values *cfg = repo_config_values(the_repository);
346 + cfg->has_symlinks = git_config_bool(var, value);
347 return 0;
348 }
349
@@ -727,5 +734,6 @@ void repo_config_values_init(struct repo_config_values *cfg)
734 cfg->attributes_file = NULL;
735 cfg->apply_sparse_checkout = 0;
736 cfg->trust_executable_bit = 1;
737 + cfg->has_symlinks = platform_has_symlinks();
738 cfg->branch_track = BRANCH_TRACK_REMOTE;
739 }
environment.h
+3 -1
@@ -92,6 +92,7 @@ struct repo_config_values {
92 char *attributes_file;
93 int apply_sparse_checkout;
94 int trust_executable_bit;
95 + int has_symlinks;
96
97 /* section "branch" config values */
98 enum branch_track branch_track;
@@ -126,6 +127,8 @@ int git_default_core_config(const char *var, const char *value,
127
128 int repo_trust_executable_bit(struct repository *repo);
129
130 +int repo_has_symlinks(struct repository *repo);
131 +
132 void repo_config_values_init(struct repo_config_values *cfg);
133
134 /*
@@ -163,7 +166,6 @@ extern char *git_work_tree_cfg;
166 /* Environment bits from configuration mechanism */
167 extern int trust_ctime;
168 extern int check_stat;
166 -extern int has_symlinks;
169 extern int minimum_abbrev, default_abbrev;
170 extern int ignore_case;
171 extern int assume_unchanged;
git-compat-util.h
+4
@@ -245,6 +245,10 @@ static inline int git_is_dir_sep(int c)
245 #define is_dir_sep git_is_dir_sep
246 #endif
247
248 +#ifndef platform_has_symlinks
249 +#define platform_has_symlinks() 1
250 +#endif
251 +
252 #ifndef offset_1st_component
253 static inline int git_offset_1st_component(const char *path)
254 {
read-cache.c
+4 -3
@@ -207,7 +207,7 @@ static unsigned int st_mode_from_ce(const struct cache_entry *ce)
207 {
208 switch (ce->ce_mode & S_IFMT) {
209 case S_IFLNK:
210 - return has_symlinks ? S_IFLNK : (S_IFREG | 0644);
210 + return repo_has_symlinks(the_repository) ? S_IFLNK : (S_IFREG | 0644);
211 case S_IFREG:
212 return (ce->ce_mode & (repo_trust_executable_bit(the_repository) ? 0755 : 0644)) | S_IFREG;
213 case S_IFGITLINK:
@@ -325,7 +325,7 @@ static int ce_match_stat_basic(const struct cache_entry *ce, struct stat *st)
325 break;
326 case S_IFLNK:
327 if (!S_ISLNK(st->st_mode) &&
328 - (has_symlinks || !S_ISREG(st->st_mode)))
328 + (repo_has_symlinks(the_repository) || !S_ISREG(st->st_mode)))
329 changed |= TYPE_CHANGED;
330 break;
331 case S_IFGITLINK:
@@ -740,7 +740,8 @@ int add_to_index(struct index_state *istate, const char *path, struct stat *st,
740 ce->ce_flags |= CE_INTENT_TO_ADD;
741
742
743 - if (repo_trust_executable_bit(istate->repo) && has_symlinks) {
743 + if (repo_trust_executable_bit(istate->repo) &&
744 + repo_has_symlinks(istate->repo)) {
745 ce->ce_mode = create_ce_mode(st_mode);
746 } else {
747 /* If there is an existing entry, pick the mode bits and type
read-cache.h
+1 -2
@@ -17,8 +17,7 @@ static inline unsigned int ce_mode_from_stat(struct repository *repo,
17 const struct cache_entry *ce,
18 unsigned int mode)
19 {
20 - extern int has_symlinks;
21 - if (S_ISREG(mode) && !has_symlinks &&
20 + if (S_ISREG(mode) && !repo_has_symlinks(repo) &&
21 ce && S_ISLNK(ce->ce_mode))
22 return ce->ce_mode;
23 if (S_ISREG(mode) && !repo_trust_executable_bit(repo)) {