Git symbolic links in Windows
Asked Answered
Y

18

360

Our developers use a mix of Windows and Unix-based OSes. Therefore, symbolic links created on Unix machines become a problem for Windows developers. In Windows (MSysGit), the symbolic link is converted to a text file with a path to the file it points to. Instead, I'd like to convert the symbolic link into an actual Windows symbolic link.

The (updated) solution I have to this is:

  • Write a post-checkout script that will recursively look for "symbolic link" text files.
  • Replace them with a Windows symbolic link (using mklink) with the same name and extension as dummy "symbolic link"
  • Ignore these Windows symbolic links by adding an entry into file .git/info/exclude

I have not implemented this, but I believe this is a solid approach to this problem.

  1. What, if any, downsides do you see to this approach?
  2. Is this post-checkout script even implementable? I.e., can I recursively find out the dummy "symlink" files Git creates?
Yaker answered 6/5, 2011 at 21:33 Comment(9)
Although Git supports symlinks, I would strongly recommend against storing them as links in your repository, especially if you're also working with that code on Windows.Jewett
@Greg Hewgill - I totally agree with you. Unfortunately, the nature of our code base requires symlinks... so removing them isn't an option for us.Yaker
You could also ask on the msysgit mailing list why they did not implement it like that in the first place.Sd
Problem here is, what happens when they add a new link in windows? Your solution is fine for adding them in linux. See my answer, hopefully you do not need to use scriptsThereby
@GregHewgill why not? Windows supports both symbolic links and junctions -- this really seems like a missing feature in the windows versions of Git to me...Explore
Creating symlinks on Windows still doesn't work, even as of Git for Windows v2.3.6..Weatherboarding
@Sd mingw/msys is old enough that it was originally designed with working on Win9x in mind. Win9x doesn't have anything resembling a symlink.Spheroidicity
@DanDascalescu Could you explain 'creating symlinks on windows still doesnt work'? Do you mean creating symlinks and having Git understand them doesn't work? Or are you trying to say symlinks on NTFS partitions on Windows is broken? I've found that Junctions in NTFS full-fill almost every need a User has on Windows, except for the two obvious ones: relative paths and files. What's broken on Windows is preventing Users in the Admin group from making Dir Type symlinks. That's absurd and is the root problem of everything on Windows re: symlinks.Belike
With "Developer Mode" enabled in Windows 10, creating symlinks doesn't require Administrator rights! (Others commented as much on lesser-voted answers, but I didn't see them. Hoping this comment is more visible to future readers.)Laurelaureano
G
122

You can find the symlinks by looking for files that have a mode of 120000, possibly with this command:

git ls-files -s | awk '/120000/{print $4}'

Once you replace the links, I would recommend marking them as unchanged with git update-index --assume-unchanged, rather than listing them in .git/info/exclude.

Goad answered 8/5, 2011 at 21:17 Comment(11)
I had to replace awk with gawk for msysgit, but otherwise it worked perfectly. Thanks!Yaker
helo ken. would you mind sharing your script that checksfor symlink text files and replaces them with symlinks on windows using mklink. while this actually works for us the --assume-unchanged part doesn't. on switching to another branch git says the symlink files are changed and need to be commited first, while git status says there are no changes..any idea?Guidepost
Here's a PowerShell one I just put together - gist.github.com/ferventcoder/7995025Follett
@Follett your script just ignores the files, it doesn't resolve them to what they should be.Willem
@Willem that is correct. Also, this was over 2 years ago. Read the comments on my particular use case, it probably doesn't apply to you.Follett
More portable and technically accurate implementation of the same approach: git ls-files -s | grep '^12'Syntax
@Syntax but no good for scripting because it doesn't strip out the first 3 columns of data to leave a list of filenames to be acted upon.Spain
@Spain There are more portable ways to print the fourth column than using GNU awk. For example: git ls-files -s | grep '^12' | cut -f2 (second tab-delimited column; other columns are space-delimited)Syntax
One liner for Cygwin/bash to mark all symlinks unchanged: for f in `git ls-files -s | awk '/120000/{print $4}'`; do git update-index --assume-unchanged $f; doneSymbolist
on windows, symlinks store the full path, so each checkout needs to create the symlinks on their own and having some version already in the repository is therefore useless. I think that means gitignoring would be better than marking as unchanged - although I haven't tested for comparison.Minorite
To summarize the problems of the proposed oneliner: it fails if there's a filename containing "120000" (regexp needs a ^), and it fails if symlink filenames contain spaces. The solution using grep and cut by Zenexer does not have these problems.Gollin
A
226

Update note

For most Windows developers struggling with symlinks and git on Windows and the issues of sharing a repo with *nix systems, this topic is a solved problem -- once you update your Windows understanding of mklink a bit and turn on Developer Mode.

See this more modern answer before digging into the following deep git hacks discussion.

Older systems:

I was asking this exact same question a while back (not here, just in general), and ended up coming up with a very similar solution to OP's proposition. I'll post the solution I ended up using.

But first I'll provide direct answers to OP's 3 questions:

Q: "What, if any, downsides do you see to this approach?"

A: There are indeed a few downsides to the proposed solution, mainly regarding an increased potential for repository pollution, or accidentally adding duplicate files while they're in their "Windows symlink" states. (More on this under "limitations" below.)

Q: "Is this post-checkout script even implementable? i.e. can I recursively find out the dummy "symlink" files git creates?"

A: Yes, a post-checkout script is implementable! Maybe not as a literal post-git checkout step, but the solution below has met my needs well enough that a literal post-checkout script wasn't necessary.

Q: "Has anybody already worked on such a script?"

A: Yes!

The Solution:

Our developers are in much the same situation as OP's: a mixture of Windows and Unix-like hosts, repositories and submodules with many git symlinks, and no native support (yet) in the release version of MsysGit for intelligently handling these symlinks on Windows hosts.

Thanks to Josh Lee for pointing out the fact that git commits symlinks with special filemode 120000. With this information it's possible to add a few git aliases that allow for the creation and manipulation of git symlinks on Windows hosts.

  1. Creating git symlinks on Windows

     git config --global alias.add-symlink '!'"$(cat <<'ETX'
     __git_add_symlink() {
       if [ $# -ne 2 ] || [ "$1" = "-h" ]; then
         printf '%b\n' \
             'usage: git add-symlink <source_file_or_dir> <target_symlink>\n' \
             'Create a symlink in a git repository on a Windows host.\n' \
             'Note: source MUST be a path relative to the location of target'
         [ "$1" = "-h" ] && return 0 || return 2
       fi
    
       source_file_or_dir=${1#./}
       source_file_or_dir=${source_file_or_dir%/}
    
       target_symlink=${2#./}
       target_symlink=${target_symlink%/}
       target_symlink="${GIT_PREFIX}${target_symlink}"
       target_symlink=${target_symlink%/.}
       : "${target_symlink:=.}"
    
       if [ -d "$target_symlink" ]; then
         target_symlink="${target_symlink%/}/${source_file_or_dir##*/}"
       fi
    
       case "$target_symlink" in
         (*/*) target_dir=${target_symlink%/*} ;;
         (*) target_dir=$GIT_PREFIX ;;
       esac
    
       target_dir=$(cd "$target_dir" && pwd)
    
       if [ ! -e "${target_dir}/${source_file_or_dir}" ]; then
         printf 'error: git-add-symlink: %s: No such file or directory\n' \
             "${target_dir}/${source_file_or_dir}" >&2
         printf '(Source MUST be a path relative to the location of target!)\n' >&2
         return 2
       fi
    
       git update-index --add --cacheinfo 120000 \
           "$(printf '%s' "$source_file_or_dir" | git hash-object -w --stdin)" \
           "${target_symlink}" \
         && git checkout -- "$target_symlink" \
         && printf '%s -> %s\n' "${target_symlink#$GIT_PREFIX}" "$source_file_or_dir" \
         || return $?
     }
     __git_add_symlink
     ETX
     )"
    

    Usage: git add-symlink <source_file_or_dir> <target_symlink>, where the argument corresponding to the source file or directory must take the form of a path relative to the target symlink. You can use this alias the same way you would normally use ln.

    E.g., the repository tree:

     dir/
     dir/foo/
     dir/foo/bar/
     dir/foo/bar/baz      (file containing "I am baz")
     dir/foo/bar/lnk_file (symlink to ../../../file)
     file                 (file containing "I am file")
     lnk_bar              (symlink to dir/foo/bar/)
    

    Can be created on Windows as follows:

     git init
     mkdir -p dir/foo/bar/
     echo "I am baz" > dir/foo/bar/baz
     echo "I am file" > file
     git add -A
     git commit -m "Add files"
     git add-symlink ../../../file dir/foo/bar/lnk_file
     git add-symlink dir/foo/bar/ lnk_bar
     git commit -m "Add symlinks"
    
  2. Replacing git symlinks with NTFS hardlinks+junctions

     git config --global alias.rm-symlinks '!'"$(cat <<'ETX'
     __git_rm_symlinks() {
       case "$1" in (-h)
         printf 'usage: git rm-symlinks [symlink] [symlink] [...]\n'
         return 0
       esac
       ppid=$$
       case $# in
         (0) git ls-files -s | grep -E '^120000' | cut -f2 ;;
         (*) printf '%s\n' "$@" ;;
       esac | while IFS= read -r symlink; do
         case "$symlink" in
           (*/*) symdir=${symlink%/*} ;;
           (*) symdir=. ;;
         esac
    
         git checkout -- "$symlink"
         src="${symdir}/$(cat "$symlink")"
    
         posix_to_dos_sed='s_^/\([A-Za-z]\)_\1:_;s_/_\\\\_g'
         doslnk=$(printf '%s\n' "$symlink" | sed "$posix_to_dos_sed")
         dossrc=$(printf '%s\n' "$src" | sed "$posix_to_dos_sed")
    
         if [ -f "$src" ]; then
           rm -f "$symlink"
           cmd //C mklink //H "$doslnk" "$dossrc"
         elif [ -d "$src" ]; then
           rm -f "$symlink"
           cmd //C mklink //J "$doslnk" "$dossrc"
         else
           printf 'error: git-rm-symlink: Not a valid source\n' >&2
           printf '%s =/=> %s  (%s =/=> %s)...\n' \
               "$symlink" "$src" "$doslnk" "$dossrc" >&2
           false
         fi || printf 'ESC[%d]: %d\n' "$ppid" "$?"
    
         git update-index --assume-unchanged "$symlink"
       done | awk '
         BEGIN { status_code = 0 }
         /^ESC\['"$ppid"'\]: / { status_code = $2 ; next }
         { print }
         END { exit status_code }
       '
     }
     __git_rm_symlinks
     ETX
     )"
    
     git config --global alias.rm-symlink '!git rm-symlinks'  # for back-compat.
    

    Usage:

     git rm-symlinks [symlink] [symlink] [...]
    

    This alias can remove git symlinks one-by-one or all-at-once in one fell swoop. Symlinks will be replaced with NTFS hardlinks (in the case of files) or NTFS junctions (in the case of directories). The benefit of using hardlinks+junctions over "true" NTFS symlinks is that elevated UAC permissions are not required in order for them to be created.

    To remove symlinks from submodules, just use git's built-in support for iterating over them:

     git submodule foreach --recursive git rm-symlinks
    

    But, for every drastic action like this, a reversal is nice to have...

  3. Restoring git symlinks on Windows

     git config --global alias.checkout-symlinks '!'"$(cat <<'ETX'
     __git_checkout_symlinks() {
       case "$1" in (-h)
         printf 'usage: git checkout-symlinks [symlink] [symlink] [...]\n'
         return 0
       esac
       case $# in
         (0) git ls-files -s | grep -E '^120000' | cut -f2 ;;
         (*) printf '%s\n' "$@" ;;
       esac | while IFS= read -r symlink; do
         git update-index --no-assume-unchanged "$symlink"
         rmdir "$symlink" >/dev/null 2>&1
         git checkout -- "$symlink"
         printf 'Restored git symlink: %s -> %s\n' "$symlink" "$(cat "$symlink")"
       done
     }
     __git_checkout_symlinks
     ETX
     )"
    
     git config --global alias.co-symlinks '!git checkout-symlinks'
    

    Usage: git checkout-symlinks [symlink] [symlink] [...], which undoes git rm-symlinks, effectively restoring the repository to its natural state (except for your changes, which should stay intact).

    And for submodules:

     git submodule foreach --recursive git checkout-symlinks
    
  4. Limitations:

    • Directories/files/symlinks with spaces in their paths should work. But tabs or newlines? YMMV… (By this I mean: don’t do that, because it will not work.)

    • If yourself or others forget to git checkout-symlinks before doing something with potentially wide-sweeping consequences like git add -A, the local repository could end up in a polluted state.

      Using our "example repo" from before:

      echo "I am nuthafile" > dir/foo/bar/nuthafile
      echo "Updating file" >> file
      git add -A
      git status
      # On branch master
      # Changes to be committed:
      #   (use "git reset HEAD <file>..." to unstage)
      #
      #       new file:   dir/foo/bar/nuthafile
      #       modified:   file
      #       deleted:    lnk_bar           # POLLUTION
      #       new file:   lnk_bar/baz       # POLLUTION
      #       new file:   lnk_bar/lnk_file  # POLLUTION
      #       new file:   lnk_bar/nuthafile # POLLUTION
      #
      

      Whoops...

      For this reason, it's nice to include these aliases as steps to perform for Windows users before-and-after building a project, rather than after checkout or before pushing. But each situation is different. These aliases have been useful enough for me that a true post-checkout solution hasn't been necessary.

References:

http://git-scm.com/book/en/Git-Internals-Git-Objects

http://technet.microsoft.com/en-us/library/cc753194

Last Update: 2019-03-13

  • POSIX compliance (well, except for those mklink calls, of course) — no more Bashisms!
  • Directories and files with spaces in them are supported.
  • Zero and non-zero exit status codes (for communicating success/failure of the requested command, respectively) are now properly preserved/returned.
  • The add-symlink alias now works more like ln(1) and can be used from any directory in the repository, not just the repository’s root directory.
  • The rm-symlink alias (singular) has been superseded by the rm-symlinks alias (plural), which now accepts multiple arguments (or no arguments at all, which finds all of the symlinks throughout the repository, as before) for selectively transforming git symlinks into NTFS hardlinks+junctions.
  • The checkout-symlinks alias has also been updated to accept multiple arguments (or none at all, == everything) for selective reversal of the aforementioned transformations.

Final Note: While I did test loading and running these aliases using Bash 3.2 (and even 3.1) for those who may still be stuck on such ancient versions for any number of reasons, be aware that versions as old as these are notorious for their parser bugs. If you experience issues while trying to install any of these aliases, the first thing you should look into is upgrading your shell (for Bash, check the version with CTRL+X, CTRL+V). Alternatively, if you’re trying to install them by pasting them into your terminal emulator, you may have more luck pasting them into a file and sourcing it instead, e.g. as

. ./git-win-symlinks.sh
Again answered 25/5, 2013 at 21:27 Comment(12)
this is a great and wonderful script, but is there any reason why this should affix the word "git" randomly at the end of some of my files that I create with git add-symlink?Worsted
Also, if your file name contains "-h" you get the usage. Still very a useful script!Worsted
Your git add-symlink recipe has been fantastically valuable for me. Many thanks.Variant
Thx for sharing, I just come up with a script inspired from this, that can create symlink using relative path to pwd instead of repository root. coderwall.com/p/z86txw/make-symlink-on-windows-in-a-git-repoAudley
In my case, there is an issue with the current git add-symlink resulting in this issue : ERROR: Target dest does not exists; not creating invalid symlink.. I think you are seeking for $dst_arg instead of $src_arg in the "if" statementKind
Is there any way to automatically execute these scripts using hooks?Fiorin
@Kind remove that check from the script... it's obviously incorrect because the TARGET must be relative to the link ... so it won't work .... without doing some sort of dirname(LINK) + TARGET. It works otherwise and is very helpful when doing multiplatform development in git land.!Aneto
Can't git automate this?Cabezon
Is there an update to this? For git add-symlink /quasar/src-cordova/ ../../ I get __git_add_symlink() ... -c: line 1: syntax error: unexpected end of fileTheology
Brief review (not mine) of above script: ..."My gripe with rm-symlinks is that it breaks certain git workflow options. I can no longer reset --hard HEAD to go back to a clean working copy, nor can I git add --all or git add . because all of those commands affect the symlink folders' --assume-unchanged property that rm-symlinks provides." ...Nephoscope
Take an updoot, you sir are a wizard.Cristal
Anyone that encounters issues with the add-symlink version not adding properly: You need to remove the space before the ETX on the second to last line. Because of the formatting there is an extra space there, that should not be there.Bawcock
K
168

2020+ TL;DR Answer

  1. Enable "Developer Mode" in Windows 10/11 -- gives mklink permissions
  2. Ensure symlinks are enabled in git with (at least) one of
    • System setting: check the checkbox when installing msysgit
    • Global setting: git config --global core.symlinks true
    • Local setting: git config core.symlinks true

Be careful, support for symlinks in git on Windows is relatively new. There are some bugs that still affect some git clients. Notably, symlinks with “parent” (..) paths are mangled during checkout in some programs because of a (fixed) regression in libgit2. For instance, GitKraken is affected by this because they are waiting on nodegit to update libgit2 from v0.x (regression) to v1.x (fixed).


Recreate missing/broken symlinks

Various levels of success have been reported across multiple git clients with one of these (increasingly forceful and "dangerous") options

  • Checkout: git checkout -- path/to/symlink
  • Restore (since git v2.23.0): git restore -- path/to/symlink
  • Switch branches (away and back)
  • Hard Reset: git reset --hard
  • Delete local repository and clone again

Troubleshooting

git config --show-scope --show-origin core.symlinks will show you the level (aka "scope") the setting is set, where the configuration file (aka "origin") that is persisting it is, and the current value of the setting. Most likely a "local" configuration is overriding the "global" or "system" setting. git config --unset core.symlinks will clear a "local" setting allowing a higher level setting to take effect.

Kaine answered 15/1, 2020 at 23:54 Comment(9)
All of my local repositories have core.symlinks=false which would override your solution. Any idea what is producing this local configuration automatically? Possibly installing Git for Windows without checking the checkbox?Philan
@Philan which git client(s) do you have installed? Maybe some tooling is doing this? Is this true on a fresh clone?Kaine
Also you have to re-clone repository to make symlinks work with itSatire
Thank you @Satire re-clone did the trick. 'Switching branches will force the recreation of missing symlinks.' didn't work.Potsdam
No need to re-clone, fixing the repo's config should be enough. This helps if git config core.symlinks still returns false in your repo, while git config --global core.symlinks says true. Run git config --unset core.symlinks; note: no --global!Julianejuliann
Worked for me on Windows 10. Thanks a lot!Porker
If you don't want to enable developer mode, you can simply set symlink permission to your account: Press Win + R, type gpedit.msc, hit OK. Then navigate to Local Comp Policy > Computer Configuration > Windows Settings > Security Settings > Local Policies > User Rights Assignment > Create symbolic links, open that option and add your user account.Digest
Although the answer from @HonzaVojtěch is correct, on Home editions of Windows 10 and 11, it is necessary to install gpedit.msc before calling it. Some install instructions can be found at this link: answers.microsoft.com/en-us/windows/forum/all/…Bornstein
Windows mklink distinguishes between "file symbolic links" and "directory symbolic links" (mklink /d). If a repo's symbolic link refers to a directory defined in a submodule, and the submodule has not yet been downloaded, git creates a file symbolic link, which won't work. This results in the vexing "The directory name is invalid" error. The fix it to delete the symlink in the working copy, then restore it with git checkout. Since the target directory now exists, git (correctly) creates a directory symbolic link. This process doesn't result in any change to the repository.Alanna
G
122

You can find the symlinks by looking for files that have a mode of 120000, possibly with this command:

git ls-files -s | awk '/120000/{print $4}'

Once you replace the links, I would recommend marking them as unchanged with git update-index --assume-unchanged, rather than listing them in .git/info/exclude.

Goad answered 8/5, 2011 at 21:17 Comment(11)
I had to replace awk with gawk for msysgit, but otherwise it worked perfectly. Thanks!Yaker
helo ken. would you mind sharing your script that checksfor symlink text files and replaces them with symlinks on windows using mklink. while this actually works for us the --assume-unchanged part doesn't. on switching to another branch git says the symlink files are changed and need to be commited first, while git status says there are no changes..any idea?Guidepost
Here's a PowerShell one I just put together - gist.github.com/ferventcoder/7995025Follett
@Follett your script just ignores the files, it doesn't resolve them to what they should be.Willem
@Willem that is correct. Also, this was over 2 years ago. Read the comments on my particular use case, it probably doesn't apply to you.Follett
More portable and technically accurate implementation of the same approach: git ls-files -s | grep '^12'Syntax
@Syntax but no good for scripting because it doesn't strip out the first 3 columns of data to leave a list of filenames to be acted upon.Spain
@Spain There are more portable ways to print the fourth column than using GNU awk. For example: git ls-files -s | grep '^12' | cut -f2 (second tab-delimited column; other columns are space-delimited)Syntax
One liner for Cygwin/bash to mark all symlinks unchanged: for f in `git ls-files -s | awk '/120000/{print $4}'`; do git update-index --assume-unchanged $f; doneSymbolist
on windows, symlinks store the full path, so each checkout needs to create the symlinks on their own and having some version already in the repository is therefore useless. I think that means gitignoring would be better than marking as unchanged - although I haven't tested for comparison.Minorite
To summarize the problems of the proposed oneliner: it fails if there's a filename containing "120000" (regexp needs a ^), and it fails if symlink filenames contain spaces. The solution using grep and cut by Zenexer does not have these problems.Gollin
H
107

The most recent version of Git SCM (tested on version 2.11.1) allows to enable symbolic links. But you have to clone the repository with the symbolic links again git clone -c core.symlinks=true <URL>. You need to run this command with administrator rights. It is also possible to create symbolic links on Windows with mklink.

Check out the wiki.

Enter image description here

Homestead answered 9/2, 2017 at 12:52 Comment(8)
This didn't work for me. I reinstalled git for Windows, remember to check the symlink checkbox and clone my project again. My tslint.json file referencing the file in the parent directory still contains ../tslint.json. Pity, because this really looked like the easiest of all of the solutions proposed in there.Ofeliaofella
@JanAagaard You have to clone it like this: git clone -c core.symlinks=true <URL> And on Windows you have to run it with administrator rights.Homestead
Have you found any way of making this work for normal users? According to the wiki page it should be possible but it does not work for me.Fiorin
@Fiorin "Launch gpedit.msc (i.e. the group policy editor) and add the account(s) to Computer configuration\Windows Setting\Security Settings\Local Policies\User Rights Assignment\Create symbolic links."Homestead
@sirlunchalot Thanks for the help. I have since come to realise that my problem is that my user is part of the Administrators group and that this property has no effect for these users. They require UAC elevation which git does not do.Fiorin
Admin rights are not necessary in "Developer Mode" in Windows 10 Creators Update. Thanks @dennis in his comment.Milden
Indeed there is bug in that UAC elevation isn't even possible in some Windows's unless you are not an admin but do have elevated rights. Better to use junctions and hard links like in the previous answer. Git scm should probably have another checkbox "Enable UAC workaround" ...Aneto
Running as admin with enabled symbolic links, the 2 created symbolic links were broken, I deleted them and manually created them, git status told me one of them was modified, I did a checkout on it and now it is working, really weird stuffIsopropanol
H
30

So as things have changed with Git since a lot of these answers were posted, here is the correct instructions to get symbolic links working correctly in Windows as of:

August 2018


1. Make sure Git is installed with symbolic link support

During the install of Git on Windows

2. Tell Bash to create hardlinks instead of symbolic links

(git folder)/etc/bash.bashrc

Add to bottom - MSYS=winsymlinks:nativestrict

3. Set Git config to use symbolic links

git config core.symlinks true

or

git clone -c core.symlinks=true <URL>

Note: I have tried adding this to the global Git configuration and at the moment it is not working for me, so I recommend adding this to each repository...

4. pull the repository

Note: Unless you have enabled developer mode in the latest version of Windows 10, you need to run Bash as administrator to create symbolic links

5. Reset all symbolic links (optional)

If you have an existing repository, or are using submodules you may find that the symbolic links are not being created correctly so to refresh all the symbolic links in the repository you can run these commands.

find -type l -delete
git reset --hard

Note: this will reset any changes since the last commit, so make sure you have committed first

Hoppe answered 30/8, 2018 at 12:31 Comment(1)
Enabling developer mode on Windows: gist.github.com/huenisys/…Vauban
R
18

It ought to be implemented in MSysGit, but there are two downsides:

  • Symbolic links are only available in Windows Vista and later (it should not be an issue in 2011, and yet it is...), since older versions only support directory junctions.
  • (the big one) Microsoft considers symbolic links a security risk and so only administrators can create them by default. You'll need to elevate privileges of the Git process or use fstool to change this behavior on every machine you work on.

I did a quick search and there is work being actively done on this; see issue 224.

Riggs answered 8/5, 2011 at 22:6 Comment(5)
Update: for the above reasons, the issue was closed as wontfix. The discussion indicates that a fix could be accepted with some more work on the patch (say, using symlinks only if they work).Incident
A.) currently msysgit doesn't support symlinks at all -- so why not have it detect "oh you're on vista with NTFS let me use symlinks" or "oh, you're on an OS that supports junctions with NTFS, let me use those", or "oh, you're on windows 98/fat32, let me fallback to just not having this feature and giving you a warning instead!" and then B.) Pretty much all of microsoft's dev. tools don't work right (at least not for all of their features) if you don't run them as an administrator -- everyone in IT knows that developers need to be admins on their own boxes.Explore
While I do run certain machines in the Admin account, I don't follow that philosophy on my development machine. I always run as a regular user with UAC-enabled. I keep a separate console open for operations that require elevated privileges. As for getting this implemented, it comes down to someone (like you) volunteering to implement it. The msysgit developers are not known for charity...Riggs
@Riggs A User has to open a command prompt with "Run as Administrator." It is almost quite literally running as the Administrator User which completely changes the Environment. There is not a way to run 'mklink /d' as a User who also is in the Admin group. It won't UAC prompt. It will fail, always. Only two ways it works: Literally as the Administrator user (RunAs Verb), or a non-admin User with group policy change. Junctions should be the default and should be recognized by all tools. The 'security risk' is that symlinks on Windows can 'redirect' SMB shares. This is a pain and cruel.Belike
Announced Dec 2016, Symlinks in Windows 10 is no longer an Administrator action. blogs.windows.com/buildingapps/2016/12/02/symlinks-windows-10/…Sentience
S
18

Short answer: They are now supported nicely, if you can enable developer mode.

From Symlinks in Windows 10!:

Now in Windows 10 Creators Update, a user (with admin rights) can first enable Developer Mode, and then any user on the machine can run the mklink command without elevating a command-line console.

What drove this change? The availability and use of symlinks is a big deal to modern developers:

Many popular development tools like git and package managers like npm recognize and persist symlinks when creating repos or packages, respectively. When those repos or packages are then restored elsewhere, the symlinks are also restored, ensuring disk space (and the user’s time) isn’t wasted.

It is easy to overlook with all the other announcements of the "Creator's update", but if you enable Developer Mode, you can create symbolic links without elevated privileges. You might have to reinstall Git and make sure symbolic link support is enabled, as it's not by default.

Symbolic Links aren't enabled by default

Seasoning answered 19/4, 2018 at 4:58 Comment(1)
gpedit.msc -> Local Computer Policy -> Computer Configuration -> Windows Settings -> Security Settings -> Local Policies -> User Rights Assignment has been the canonical way to assign user rights like SeCreateSymbolicLink and friends for ages. Other than ntrights.exe from the Resource Kit or the PowerShell ...Bushtit
T
10

I would suggest you don't use symlinks within the repository. Store the actual content inside the repository and then place symlinks out side the repository that point to the content.

So let’s say you are using a repository to compare hosting your site on a Unix-like system with hosting on Windows. Store the content in your repository, let’s say /httpRepoContent and c:\httpRepoContent with this being the folder that is synced via Git, SVN, etc.

Then, replace the content folder of you web server (/var/www and c:\program files\web server\www {names don't really matter, edit if you must}) with a symbolic link to the content in your repository. The web servers will see the content as actually in the 'right' place, but you get to use your source control.

However, if you need to use symlinks with in the repository, you will need to look into something like some sort of pre/post commit scripts. I know you can use them to do things, such as parse code files through a formatter for example, so it should be possible to convert the symlinks between platforms.

If any one knows a good place to learn how to do these scripts for the common source controls, SVN, Git, and MG, then please do add a comment.

Thereby answered 9/2, 2012 at 9:45 Comment(4)
In the end, I chose this approach to create a symlink-out folder and create the symbolic links to where the original file used to be. The other approach didn't work even after I changed .git/config setting core.symlinks = true. Only the symbolic link file was saved to the repo and not the data. Also had problems with folder timestamps on the symbolic link so git bash never saw when a file changed within the folder.Lepus
@Lepus what you might have been seeing, I guess, is that the link was within the repo, and so git saved it, simples. The problem though is that the target was outside of the repo, and git does not follow the link to the target data. On linux you have a type of link that would work for this, it's basically let's you have two paths to the same data stored on disk; I've a feeling newish windows can do this now. Either way though, I still don't think it's going to do what people want.Thereby
@Thereby This is not a solution, but a workaround. However, sometimes this is not an option. I have a repository with git-annex an all its architecture works because of symbolic links.Parapet
What is "MG"? Do you mean Mercurial (hg)?Winsome
D
9

For those using Cygwin on Windows Vista, Windows 7, or above, the native git command can create "proper" symbolic links that are recognized by Windows apps such as Android Studio. You just need to set the CYGWIN environment variable to include winsymlinks:native or winsymlinks:nativestrict as such:

export CYGWIN="$CYGWIN winsymlinks:native"

The downside to this (and a significant one at that) is that the Cygwin shell has to be "Run as Administrator" in order for it to have the OS permissions required to create those kind of symbolic links. Once they're created, though, no special permissions are required to use them. As long they aren't changed in the repository by another developer, git thereafter runs fine with normal user permissions.

Personally, I use this only for symbolic links that are navigated by Windows applications (i.e., non-Cygwin) because of this added difficulty.

For more information on this option, see this Stack Overflow question: How to make a symbolic link with Cygwin in Windows 7

Disfigurement answered 26/5, 2015 at 11:43 Comment(3)
Cygwin official docs discourage usage of winsymlinks:native. With "Developer mode" seems you no longer need to run with elevated privileges in you are in Windows 10.Chapple
this didn't help me, but export MSYS=winsymlinks:nativestrict didGundry
@JerryGreen yes, the environment variables CYGWIN and MSYS are only looked at by their respective tools; if you're running Git-for-Windows (built with MSys2) then setting CYGWIN won't matter. The good news is that MSys was forked from Cygwin so the values of those environment variables are often the same or at least overlapping.Sophist
P
9

I just tried with Git 2.30.0 (released 2020-12-28).

This is not a full answer, but a few useful tidbits nonetheless. (Feel free to cannibalize for your own answer.)

Git Wiki Entry

There's a documentation link when installing Git for Windows

Git installer screenshot

[ ] Enable symbolic links
Enable symbolic links (requires the SeCreateSymbolicLink permission).
Please note that existing repositories are unaffected by this setting.

This link takes you here: https://github.com/git-for-windows/git/wiki/Symbolic-Links -- And this is quite a longish discussion.

FYI: There are at least three "kinds of links". And just to highlight an important aspect of this wiki entry: I didn't know this, but there are several ways all of which are "kind of" symbolic links on the surface, but on a technical level are very different:

  • git bash's "ln -s" Which just copies things. Oh, boy. That was unexpected to me. (FYI: Plain Cygwin does not do this. Mobaxterm does not do this. Instead they both create something that their stat command actually recognizes as "symbolic link".)
  • cmd.exe's builtin "mklink" command with the "/D" parameter Which creates a directory symbolic link. (See the Microsoft documentation)
  • cmd.exe's builtin "mklink" command with the "/J" parameter. Which creates a directory junction AKA soft link AKA reparse point. (See the Microsoft documentation.)

Release Notes Entry

Also symbolic links keep popping up in the release notes. As of 2.30.0 this here is still listed as a "Known issue":

On Windows 10 before 1703, or when Developer Mode is turned off, special permissions are required when cloning repositories with symbolic links, therefore support for symbolic links is disabled by default. Use git clone -c core.symlinks=true <URL> to enable it, see details here.

Playmate answered 4/1, 2021 at 14:3 Comment(0)
T
8

Here is a batch script for converting symbolic link in repository, for files only, based on Josh Lee's answer. A script with some additional check for administrator rights is at https://gist.github.com/Quazistax/8daf09080bf54b4c7641.

@echo off
pushd "%~dp0"
setlocal EnableDelayedExpansion

for /f "tokens=3,*" %%e in ('git ls-files -s ^| findstr /R /C:"^120000"') do (
     call :processFirstLine %%f
)
REM pause
goto :eof

:processFirstLine
@echo.
@echo FILE:    %1

dir "%~f1" | find "<SYMLINK>" >NUL && (
  @echo FILE already is a symlink
  goto :eof
)

for /f "usebackq tokens=*" %%l in ("%~f1") do (
  @echo LINK TO: %%l

  del "%~f1"
  if not !ERRORLEVEL! == 0 (
    @echo FAILED: del
    goto :eof
  )

  setlocal
  call :expandRelative linkto "%1" "%%l"
  mklink "%~f1" "!linkto!"
  endlocal
  if not !ERRORLEVEL! == 0 (
    @echo FAILED: mklink
    @echo reverting deletion...
    git checkout -- "%~f1"
    goto :eof
  )

  git update-index --assume-unchanged "%1"
  if not !ERRORLEVEL! == 0 (
    @echo FAILED: git update-index --assume-unchanged
    goto :eof
  )
  @echo SUCCESS
  goto :eof
)
goto :eof

:: param1 = result variable
:: param2 = reference path from which relative will be resolved
:: param3 = relative path
:expandRelative
  pushd .
  cd "%~dp2"
  set %1=%~f3
  popd
goto :eof
Thule answered 27/2, 2015 at 0:3 Comment(1)
An undocumented answer is really not much use when there are already such lengthy and verbose answers present.Protostele
R
4

I use symbolic links all the time between my document root and Git repository directory. I like to keep them separate. On Windows I use the mklink /j option. The junction seems to let Git behave normally:

>mklink /j <location(path) of link> <source of link>

For example:

>mklink /j c:\gitRepos\Posts C:\Bitnami\wamp\apache2\htdocs\Posts

Ricardaricardama answered 16/6, 2015 at 16:55 Comment(2)
Be very careful with Windows Explorer and junctions; it doesn't differentiate junctions from the base location and a delete will recurse into the target and delete it's contents, whereas the delete of a symlink will just remove the symlink. Just a trap for the unwary.Lamoree
Actually, just tested this on the latest of Windows7, and it no longer does it, so handling of junctions has been improved sometime in the last few years.Lamoree
T
4

I was looking for an easy solution to deal with the Unix symbolic links on Windows. Thank you very much for the Git aliases in previous answers.

There is one little optimization that can be done to the rm-symbolic links, so that it doesn't delete the files in the destination folder in case the alias is run a second time accidentally. Please observe the new if condition in the loop to make sure the file is not already a link to a directory before the logic is run.

git config --global alias.rm-symlinks '!__git_rm_symlinks(){
for symlink in $(git ls-files -s | egrep "^120000" | cut -f2); do
    *if [ -d "$symlink" ]; then
      continue
    fi*
    git rm-symlink "$symlink"
    git update-index --assume-unchanged "$symlink"
done
}; __git_rm_symlinksenter
Threeply answered 25/2, 2016 at 23:18 Comment(0)
A
3

One simple trick we use is to just call git add --all twice in a row.

For example, our Windows 7 commit script calls:

git add --all
git add --all

The first add treats the link as text and adds the folders for delete.

The second add traverses the link correctly and undoes the delete by restoring the files.

It's less elegant than some of the other proposed solutions, but it is a simple fix to some of our legacy environments that got symbolic links added.

Amaze answered 29/9, 2017 at 17:32 Comment(0)
P
1

Here's a PowerShell script to replace Unix symbolic links with Windows.

# This fixes permission denied errors you might get when
# there are Git symbolic links being used on repositories that
# you share in both POSIX (usually the host) and Windows (VM).
#
# This is not an issue if you are checking out the same
# repository separately in each platform. This is only an issue
# when it's the same working set (AKA make a change without
# committing on OS X, go to Windows VM and Git status would show
# you that change).
#
# Based on this answer on Stack Overflow: https://mcmap.net/q/46993/-git-symbolic-links-in-windows
#
# No warranties. Good luck.
#
# NOTE: It must be run in elevated PowerShell

$ROOT = $PWD

$symlinks = &git ls-files -s | gawk '/120000/{print $4}'
foreach ($symlink in $symlinks) {
  $content = &Get-Content $symlink
  $content = $content.Replace("/", "\")
  $filename = $symlink.Split("/")[-1]
  cd (dirname $symlink)
  rm $filename
  echo Linking $content -> $filename
  New-Item -ItemType SymbolicLink -Path $filename -Target $content
  &git update-index --assume-unchanged $symlink
  cd $ROOT
}
Poindexter answered 29/8, 2021 at 22:53 Comment(0)
A
1

Another option:

Install a WSL2 distribution on your windows machine, and install the new Windows terminal, then do your git clone in a Linux filesystem. symlinks will then just work. You'll also not have to deal with DOS <> Unix end of line conversion issues.

You can still access that git repo using any Windows tools that you want, as WSL2 Windows integration is amazing.

You'll also get the power of a Linux command line if you want it, without having to use the old (slow in comparison) cygwin tools.

Alveta answered 24/11, 2023 at 14:14 Comment(0)
H
0

Found my way here for advice on symlinks for a python project. Wanted to use symlinks to mylib.py living two parent directories above. After several fails, these steps work on github, Windows 11 and Hugging Face Spaces.

  1. mklink/J mylib ....\mylib
  2. git add mylib\mylib.py
  3. add to python: sys.path.append(str(pathlib.Path.cwd().joinpath('mylib'))) # global scope
  4. import mylib
Homage answered 29/7, 2023 at 3:35 Comment(0)
D
0
function mklink()
{
#from: zeltrex.com
#parameters
    local flags="$1"
    local target="$(pwd)/$2"
    local link="$(pwd)/$3"
#constants
#variables
    local win_target_path="$( cygpath -w $target )"
    local win_link_path="$( cygpath -w $link )"
    local cmd="mklink $flags $win_link_path $win_target_path"
#validate parameters

#main logic
    trace "LOCATION Path: from [$target]->[$win_target_path]"
    trace "LINK Path:     from [$link]->[$win_link_path]"
    trace "executing in cmd:> $cmd"
    powershell.exe -Command Start-Process \
    -FilePath cmd.exe \
    -WindowStyle Hidden \
    -ArgumentList \"/c $cmd \"
    # the same command could be run more concisely as:
    # powershell start cmd \"/k mklink $link $target\" -v runas
}
Doting answered 29/9, 2023 at 12:55 Comment(1)
As it’s currently written, your answer is unclear. Please edit to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers in the help center.Piselli

© 2022 - 2024 — McMap. All rights reserved.