Real Symlinks in Git Bash on Windows

In short. You have probably run ln -s in Git Bash and ended up with a copy of the file rather than a link. Bring dotfiles that worked on Linux over to Windows and every link turns into a text file. Same cause. Four settings fix it. This post covers those four steps and the symptoms you get when one of them is missing.

Dotori Bluetooth Numpad - Your phone as a 17-key number pad

A free app I built myself. Give it a try.

A symbolic link is a signpost holding a target path

A symbolic link is not a copy of a file. It is a special file holding a path string that says "the real file is over there". You keep the original in one place. Several locations refer to it as if it were the same file. Edit the original and everything reached through the link sees the change at once.

Git stores symbolic links as they are. Where an ordinary file is recorded in mode 100644, a link goes in as mode 120000. Its content is a single target path string.

$ git ls-files -s
120000 c9c61fe1fb4b3b... 0	alias.txt
100644 10622902e19e73... 0	real.txt

The problem is on the Windows side. Windows has symbolic links too, but creating one requires the SeCreateSymbolicLinkPrivilege right. By default only administrators have it. Everything below exists to work around that constraint.

All four steps have to line up before a link is created

  1. When installing Git, tick Enable symbolic links on the last screen.
  2. Run Git Bash as administrator.
  3. Put export MSYS=winsymlinks:nativestrict in the shell environment.
  4. Add symlinks = true under [core] in ~/.gitconfig.

Steps 3 and 4 are two commands.

echo 'export MSYS=winsymlinks:nativestrict' >> ~/.bashrc
git config --global core.symlinks true

The MSYS variable is the key one. Its default is deepcopy, so when it cannot create a link it quietly copies the file instead. nativestrict creates native links only and raises an error when the privilege is missing. Because it does not hide the failure, you see the cause immediately. Open a new terminal and check like this.

$ ln -s real.txt alias.txt
$ ls -l alias.txt
lrwxrwxrwx 1 dualk 197609 8  Aug 27 16:59 alias.txt -> real.txt

A leading l and an arrow mean it worked. -rw-r--r-- means you got a copy.

Change the properties of the real exe, not the shortcut

Right-clicking and running as administrator every time is a chore. Follow the Git Bash shortcut back to the actual executable. Give that file permanent administrator rights.

  1. Trace the Git Bash shortcut to the original .exe, right-click it and choose Properties.
  2. On the Compatibility tab, tick Run this program as an administrator.

If keeping an administrator shell open all the time bothers you, there is an alternative. Developer Mode lets an ordinary user account create symbolic links. It has been there since Windows 10 Creators Update (1703). Enable it under Settings > Privacy & security > For developers, reboot, and step 3 alone is enough.

Miss a setting and links quietly become text files

The worst case is the one that produces no error. Clone a repository with core.symlinks off and the link is not created. In its place you get an ordinary file whose content is the path string.

StateStart of ls -lResult of cat alias.txt
Working linklrwxrwxrwx ... -> real.txtThe content of the original file
Broken link-rw-r--r-- (8 bytes)real.txt (a path string)

What makes it worse is that git status comes back clean. Git does not see this as a change, so it can be committed without anyone noticing. The core.symlinks=false value from the clone is baked into that repository's .git/config. Turn the global setting on later and the local setting still wins. That one repository stays broken.

git config --local --get core.symlinks    # if this prints false, that is the cause
git config --local --unset core.symlinks  # remove the local setting
git checkout -- .                          # lay the working tree out again

Summary

A symbolic link is a file holding a target path. On Windows, creating one needs a separate privilege. All four have to line up. Install option, administrator rights, MSYS=winsymlinks:nativestrict, core.symlinks=true. Miss any one of them and you get a copy instead of an error, with git status staying quiet about it.

Open Git Bash now and run echo $MSYS and git config --get core.symlinks. If both come back empty, start with the two commands above.