refs/files: deprecate writing symrefs as symbolic links

The "files" backend has the ability to store symbolic refs as symbolic links, which can be configured via "core.preferSymlinkRefs". This feature stems back from the early days: the initial implementation of symbolic refs used symlinks exclusively. The symref format was only introduced in 9b143c6e15 (Teach update-ref about a symbolic ref stored in a textfile., 2005-09-25) and made the default in 9f0bb90d16 (core.prefersymlinkrefs: use symlinks for .git/HEAD, 2006-05-02). This is all about 20 years ago, and there are no known reasons nowadays why one would want to use symlinks instead of symrefs. Mark the feature for deprecation in Git 3.0. Note that this only deprecates _writing_ symrefs as symbolic links. Reading such symrefs is still supported for now. Signed-off-by: Patrick Steinhardt <ps@pks.im> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Patrick Steinhardt committed Oct 15, 2025 at 08:26 UTC f570bd91b3b2c6c5ef2035e3ce3ed76e613e74a7
4 files changed +63 -5
Documentation/BreakingChanges.adoc
+20
@@ -295,6 +295,26 @@ The command will be removed.
295 +
296 cf. <xmqqa59i45wc.fsf@gitster.g>
297
298 +* Support for `core.preferSymlinkRefs=true` has been deprecated and will be
299 + removed in Git 3.0. Writing symbolic refs as symbolic links will be phased
300 + out in favor of using plain files using the textual representation of
301 + symbolic refs.
302 ++
303 +Symbolic references were initially always stored as a symbolic link. This was
304 +changed in 9b143c6e15 (Teach update-ref about a symbolic ref stored in a
305 +textfile., 2005-09-25), where a new textual symref format was introduced to
306 +store those symbolic refs in a plain file. In 9f0bb90d16
307 +(core.prefersymlinkrefs: use symlinks for .git/HEAD, 2006-05-02), the Git
308 +project switched the default to use the textual symrefs in favor of symbolic
309 +links.
310 ++
311 +The migration away from symbolic links has happened almost 20 years ago by now,
312 +and there is no known reason why one should prefer them nowadays. Furthermore,
313 +symbolic links are not supported on some platforms.
314 ++
315 +Note that only the writing side for such symbolic links is deprecated. Reading
316 +such symbolic links is still supported for now.
317 +
318 == Superseded features that will not be deprecated
319
320 Some features have gained newer replacements that aim to improve the design in
Documentation/config/core.adoc
+3
@@ -290,6 +290,9 @@ core.preferSymlinkRefs::
290 and other symbolic reference files, use symbolic links.
291 This is sometimes needed to work with old scripts that
292 expect HEAD to be a symbolic link.
293 ++
294 +This configuration is deprecated and will be removed in Git 3.0. Symbolic refs
295 +will always be written as textual symrefs.
296
297 core.alternateRefsCommand::
298 When advertising tips of available history from an alternate, use the shell to
refs/files-backend.c
+17 -2
@@ -2113,20 +2113,35 @@ static int commit_ref_update(struct files_ref_store *refs,
2113 return 0;
2114 }
2115
2116 -#ifdef NO_SYMLINK_HEAD
2116 +#if defined(NO_SYMLINK_HEAD) || defined(WITH_BREAKING_CHANGES)
2117 #define create_ref_symlink(a, b) (-1)
2118 #else
2119 static int create_ref_symlink(struct ref_lock *lock, const char *target)
2120 {
2121 + static int warn_once = 1;
2122 + char *ref_path;
2123 int ret = -1;
2124
2123 - char *ref_path = get_locked_file_path(&lock->lk);
2125 + ref_path = get_locked_file_path(&lock->lk);
2126 unlink(ref_path);
2127 ret = symlink(target, ref_path);
2128 free(ref_path);
2129
2130 if (ret)
2131 fprintf(stderr, "no symlink - falling back to symbolic ref\n");
2132 +
2133 + if (warn_once)
2134 + warning(_("'core.preferSymlinkRefs=true' is nominated for removal.\n"
2135 + "hint: The use of symbolic links for symbolic refs is deprecated\n"
2136 + "hint: and will be removed in Git 3.0. The configuration that\n"
2137 + "hint: tells Git to use them is thus going away. You can unset\n"
2138 + "hint: it with:\n"
2139 + "hint:\n"
2140 + "hint:\tgit config unset core.preferSymlinkRefs\n"
2141 + "hint:\n"
2142 + "hint: Git will then use the textual symref format instead."));
2143 + warn_once = 0;
2144 +
2145 return ret;
2146 }
2147 #endif
t/t0600-reffiles-backend.sh
+23 -3
@@ -477,9 +477,29 @@ test_expect_success SYMLINKS 'symref transaction supports symlinks' '
477 prepare
478 commit
479 EOF
480 - git update-ref --no-deref --stdin <stdin &&
481 - test_path_is_symlink .git/TEST_SYMREF_HEAD &&
482 - test "$(test_readlink .git/TEST_SYMREF_HEAD)" = refs/heads/new
480 + git update-ref --no-deref --stdin <stdin 2>err &&
481 + if test_have_prereq WITH_BREAKING_CHANGES
482 + then
483 + test_path_is_file .git/TEST_SYMREF_HEAD &&
484 + echo "ref: refs/heads/new" >expect &&
485 + test_cmp expect .git/TEST_SYMREF_HEAD &&
486 + test_must_be_empty err
487 + else
488 + test_path_is_symlink .git/TEST_SYMREF_HEAD &&
489 + test "$(test_readlink .git/TEST_SYMREF_HEAD)" = refs/heads/new &&
490 + cat >expect <<-EOF &&
491 + warning: ${SQ}core.preferSymlinkRefs=true${SQ} is nominated for removal.
492 + hint: The use of symbolic links for symbolic refs is deprecated
493 + hint: and will be removed in Git 3.0. The configuration that
494 + hint: tells Git to use them is thus going away. You can unset
495 + hint: it with:
496 + hint:
497 + hint: git config unset core.preferSymlinkRefs
498 + hint:
499 + hint: Git will then use the textual symref format instead.
500 + EOF
501 + test_cmp expect err
502 + fi
503 '
504
505 test_expect_success 'symref transaction supports false symlink config' '