git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v7 4/4] environment: move has_symlinks into repo_config_values

From
Tian Yuchen <cat@malon.dev>
Date
Jul 17, 2026, 06:35 UTC
Message-ID
<20260717063559.1633567-5-cat@malon.dev>
In-Reply-To
<20260717063559.1633567-1-cat@malon.dev>

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>
---
 apply.c           |  2 +-
 combine-diff.c    |  2 +-
 compat/mingw.c    | 17 +++++++++++++----
 compat/mingw.h    |  3 +++
 entry.c           |  2 +-
 environment.c     | 16 ++++++++++++++--
 environment.h     |  4 +++-
 git-compat-util.h |  4 ++++
 read-cache.c      |  7 ++++---
 read-cache.h      |  3 +--
 10 files changed, 45 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..f2854b4cd8 100644
--- a/entry.c
+++ b/entry.c
@@ -321,7 +321,7 @@ 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 : NULL) || to_tempfile)
 			goto write_file_entry;
 
 		ret = symlink(new_blob, path);
diff --git a/environment.c b/environment.c
index 75069a884d..760689d6e7 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,17 @@ int repo_trust_executable_bit(struct repository *repo)
 		1;
 }
 
+int repo_has_symlinks(struct repository *repo)
+{
+	if (!repo)
+		repo = the_repository;
+
+	if (!repo->gitdir)
+		return platform_has_symlinks();
+
+	return repo_config_values(repo)->has_symlinks;
+}
+
 int have_git_dir(void)
 {
 	return startup_info->have_repository
@@ -336,7 +346,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 +738,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 72b59fd89c..ef64a783b0 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);
 
 /*
@@ -165,7 +168,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 5024814bd4..333a5acf33 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 1f8b5ed15f..c2c3c2e6cc 100644
--- a/read-cache.c
+++ b/read-cache.c
@@ -206,7 +206,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:
@@ -324,7 +324,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:
@@ -739,7 +739,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)) {
-- 
2.43.0
Previous: Tian YuchenNext: Tian Yuchen
Message 57 of 62 in “environment.c: migrate 'trust_executable_bit' into 'repo_config_values'”
  1. 0/4 environment.c: migrate 'trust_executable_bit' into 'repo_config_values'Tian Yuchen, May 30, 2026
  2. 2/4 read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'Tian Yuchen, May 30, 2026
  3. Patrick SteinhardtJun 4, 2026
  4. Tian YuchenJun 10, 2026
  5. 3/4 environment: move 'trust_executable_bit' into repo_config_valuesTian Yuchen, May 30, 2026
  6. Christian CouderMay 30, 2026
  7. Junio C HamanoMay 30, 2026
  8. Tian YuchenJun 1, 2026
  9. Tian YuchenJun 1, 2026
  10. Junio C HamanoJun 9, 2026
  11. 4/4 read-cache: pass 'istate' to stat/mode helper functionsTian Yuchen, May 30, 2026
  12. Christian CouderMay 30, 2026
  13. 1/4 read-cache: remove redundant extern declarationsTian Yuchen, May 30, 2026
  14. 0/3 environment: migrate 'trust_executable_bit' into 'repo_config_values'Tian Yuchen, Jun 10, 2026
  15. 1/3 read-cache: remove redundant extern declarationsTian Yuchen, Jun 10, 2026
  16. 2/3 read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'Tian Yuchen, Jun 10, 2026
  17. Christian CouderJun 12, 2026
  18. 3/3 environment: move trust_executable_bit into repo_config_valuesTian Yuchen, Jun 10, 2026
  19. Tian YuchenJun 11, 2026
  20. Christian CouderJun 12, 2026
  21. Junio C HamanoJun 12, 2026
  22. 0/3 environment: migrate 'trust_executable_bit' into 'repo_config_values'Tian Yuchen, Jun 12, 2026
  23. 2/3 read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'Tian Yuchen, Jun 12, 2026
  24. 1/3 read-cache: remove redundant extern declarationsTian Yuchen, Jun 12, 2026
  25. 3/3 environment: move trust_executable_bit into repo_config_valuesTian Yuchen, Jun 12, 2026
  26. 0/3 environment: migrate 'trust_executable_bit' into 'repo_config_values'Tian Yuchen, Jun 19, 2026
  27. 1/3 read-cache: remove redundant extern declarationsTian Yuchen, Jun 19, 2026
  28. 2/3 read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'Tian Yuchen, Jun 19, 2026
  29. 3/3 environment: move trust_executable_bit into repo_config_valuesTian Yuchen, Jun 19, 2026
  30. Junio C HamanoJun 29, 2026
  31. Tian YuchenJul 15, 2026
  32. 0/4 environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'Tian Yuchen, Jul 15, 2026
  33. 1/4 read-cache: remove redundant extern declarationsTian Yuchen, Jul 15, 2026
  34. 2/4 read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'Tian Yuchen, Jul 15, 2026
  35. 3/4 environment: move trust_executable_bit into repo_config_valuesTian Yuchen, Jul 15, 2026
  36. 4/4 environment: move has_symlinks into repo_config_valuesTian Yuchen, Jul 15, 2026
  37. Christian CouderJul 15, 2026
  38. Junio C HamanoJul 15, 2026
  39. Junio C HamanoJul 15, 2026
  40. 0/4 environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'Tian Yuchen, Jul 16, 2026
  41. 1/4 read-cache: remove redundant extern declarationsTian Yuchen, Jul 16, 2026
  42. 2/4 read-cache: move 'ce_mode_from_stat()' to 'read-cache.c'Tian Yuchen, Jul 16, 2026
  43. Junio C HamanoJul 16, 2026
  44. 3/4 environment: move trust_executable_bit into repo_config_valuesTian Yuchen, Jul 16, 2026
  45. 4/4 environment: move has_symlinks into repo_config_valuesTian Yuchen, Jul 16, 2026
  46. Junio C HamanoJul 16, 2026
  47. 0/4 environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'Tian Yuchen, Jul 17, 2026
  48. 2/4 read-cache: pass 'repo' to 'ce_mode_from_stat()'Tian Yuchen, Jul 17, 2026
  49. SZEDER GáborJul 18, 2026
  50. Tian YuchenJul 20, 2026
  51. SZEDER GáborJul 20, 2026
  52. Tian YuchenJul 20, 2026
  53. 1/4 read-cache: remove redundant extern declarationsTian Yuchen, Jul 17, 2026
  54. 3/4 environment: move trust_executable_bit into repo_config_valuesTian Yuchen, Jul 17, 2026
  55. Junio C HamanoJul 17, 2026
  56. Tian YuchenJul 20, 2026
  57. 4/4 environment: move has_symlinks into repo_config_valuesTian Yuchen, Jul 17, 2026
  58. 0/4 environment: migrate 'trust_executable_bit' and 'has_symlinks' into 'repo_config_values'Tian Yuchen, Jul 20, 2026
  59. 1/4 read-cache: remove redundant extern declarationsTian Yuchen, Jul 20, 2026
  60. 2/4 read-cache: pass 'repo' to 'ce_mode_from_stat()'Tian Yuchen, Jul 20, 2026
  61. 3/4 environment: move trust_executable_bit into repo_config_valuesTian Yuchen, Jul 20, 2026
  62. 4/4 environment: move has_symlinks into repo_config_valuesTian Yuchen, Jul 20, 2026

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.