Jozef
Jozef

Reputation: 303

Directory symlinks change to file symlinks after pushing and pulling from Git

Directory symlinks created in a Git repo on Windows for some reason change into File symlinks once pushed to Git and re-cloned. This results in a "The directory name is invalid" error.

However, this ONLY happens when the symlink contains more than one subdirectory in its path. They continue to work fine if there's only one subdirectory. Also, they all still work fine in a Bash shell.

Here's the list in the original repo:

05/01/2019  07:50 AM <SYMLINKD> ACN [..\..\acn\Installed]
05/01/2019  08:00 AM <SYMLINKD> ACNProxy [..\..\acnproxy\bin]
04/30/2019  01:29 PM <SYMLINKD> API [..\swupdate-2\bin]
05/01/2019  08:24 AM <SYMLINKD> AnalyticsPlugin [..\..\..\analyticsinstallerplugin]
05/01/2019  08:08 AM <SYMLINKD> Encryption [..\protocols\trunk\Encryption]
05/01/2019  08:17 AM <SYMLINKD> HelpFiles [..\helpwwb6]
05/01/2019  08:34 AM <SYMLINKD> PrePackagedDatabase [..\wwb6database]
05/01/2019  09:34 AM <SYMLINKD> ToastNotifications [..\toastnotifications]
05/01/2019  08:01 AM <SYMLINKD> acnComponent [..\..\..\acn_component]
05/01/2019  07:45 AM <SYMLINKD> dante_lic_mac [..\dante_mac_fix\WWB_compressed_lic_info_files]
05/01/2019  08:05 AM <SYMLINKD> devCategory [..\..\..\devicedescriptionfiles\devCategory]
05/01/2019  08:10 AM <SYMLINKD> shared [..\..\frequencycompat\FrequencyCompatibilityCalculator]
05/01/2019  09:26 AM <SYMLINKD> shared [..\..\swupdate\src\shared]
05/01/2019  08:06 AM <SYMLINKD> skuConversion [..\..\..\skuconversion\src]

After pushing the symlinks to the remote Git server and recloning the repo, those symlinks that are pointing to a path that has more than one subdirectory listed were changed into File symlinks:

05/01/2019  09:28 PM <SYMLINK>  ACN [..\..\acn\Installed]
05/01/2019  09:28 PM <SYMLINK>  ACNProxy [..\..\acnproxy\bin]
05/01/2019  09:28 PM <SYMLINK>  API [..\swupdate-2\bin]
05/01/2019  09:28 PM <SYMLINKD> AnalyticsPlugin [..\..\..\analyticsinstallerplugin]
05/01/2019  09:28 PM <SYMLINK>  Encryption [..\protocols\trunk\Encryption]
05/01/2019  09:28 PM <SYMLINKD> HelpFiles [..\helpwwb6]
05/01/2019  09:29 PM <SYMLINKD> PrePackagedDatabase [..\wwb6database]
05/01/2019  09:28 PM <SYMLINKD> ToastNotifications [..\toastnotifications]
05/01/2019  09:28 PM <SYMLINKD> acnComponent [..\..\..\acn_component]
05/01/2019  09:28 PM <SYMLINK>  dante_lic_mac [..\dante_mac_fix\WWB_compressed_lic_info_files]
05/01/2019  09:28 PM <SYMLINK>  devCategory [..\..\..\devicedescriptionfiles\devCategory]
05/01/2019  09:28 PM <SYMLINK>  shared [..\..\frequencycompat\FrequencyCompatibilityCalculator]
05/01/2019  09:28 PM <SYMLINK>  shared [..\..\swupdate\src\shared]
05/01/2019  09:28 PM <SYMLINK>  skuConversion [..\..\..\skuconversion\src]

Can anyone explain this behavior?

In a Bash shell, all symlinks look identical (and function) in both the original and cloned repo:

lrwxrwxrwx 1 ******* 1049089 17 May  1 21:28 ./wwb6/API -> ../swupdate-2/bin
lrwxrwxrwx 1 ******* 1049089 46 May  1 21:28 ./wwb6/dante_lic_mac -> ../dante_mac_fix/WWB_compressed_lic_info_files
lrwxrwxrwx 1 ******* 1049089 19 May  1 21:28 ./wwb6/datastorage/ACN -> ../../acn/Installed
lrwxrwxrwx 1 ******* 1049089 18 May  1 21:28 ./wwb6/datastorage/ACNProxy -> ../../acnproxy/bin
lrwxrwxrwx 1 ******* 1049089 22 May  1 21:28 ./wwb6/datastorage/libdatastorage/acnComponent -> ../../../acn_component
lrwxrwxrwx 1 ******* 1049089 43 May  1 21:28 ./wwb6/datastorage/libdatastorage/devCategory -> ../../../devicedescriptionfiles/devCategory
lrwxrwxrwx 1 ******* 1049089 26 May  1 21:28 ./wwb6/datastorage/libdatastorage/skuConversion -> ../../../skuconversion/src
lrwxrwxrwx 1 ******* 1049089 29 May  1 21:28 ./wwb6/Encryption -> ../protocols/trunk/Encryption
lrwxrwxrwx 1 ******* 1049089 54 May  1 21:28 ./wwb6/FrequencyCompatibility/shared -> ../../frequencycompat/FrequencyCompatibilityCalculator
lrwxrwxrwx 1 ******* 1049089 11 May  1 21:28 ./wwb6/HelpFiles -> ../helpwwb6
lrwxrwxrwx 1 ******* 1049089 33 May  1 21:28 ./wwb6/Installation/Mac/AnalyticsPlugin -> ../../../analyticsinstallerplugin
lrwxrwxrwx 1 ******* 1049089 15 May  1 21:29 ./wwb6/PrePackagedDatabase -> ../wwb6database
lrwxrwxrwx 1 ******* 1049089 25 May  1 21:28 ./wwb6/SWUpdate/shared -> ../../swupdate/src/shared
lrwxrwxrwx 1 ******* 1049089 21 May  1 21:28 ./wwb6/ToastNotifications -> ../toastnotifications

Note that all this was done on a Windows 10 machine. However, the remote repository is located on a Linux server. I deliberately do not have Administrators rights on the Windows machine, because the developers don't have those rights either and the symlinks should work for them.

To get symlinks to work in Windows, I did the following:

After making all those changes, creating and traversing directory symlinks works fine in Windows without the user having to be in the Local Admin group. Until they've been pushed to Linux and re-downloaded that is.

Update: The symlinks that changed type from SYMLINKD to SYMLINK after a clone point to directories inside submodules. The root directory of a submodule is created when the container project is cloned, but the contents isn't pulled down until after the clone of the container project (where the symlinks live) has completed:

git clone --recursive -c core.symlinks=true ssh://<server>:7999/<repo>

It appears Windows does not recreate Directory symlinks when the target does not yet exist. It creates them of type File symlink instead. This DOES work from the Windows command line though (as it does in Linux):

mklink /d symlink ..\<some non-existing directory>\<another non-existing directory>

...works fine.

My current workaround is to simply delete and restore them:

$ find . -type l -delete
$ git checkout .
Updated 14 paths from the index

Would prefer a fix for this though.

Upvotes: 5

Views: 2186

Answers (1)

VonC
VonC

Reputation: 1323115

This seems to be a pending issue, reported in git-for-windows/git issue 1027

Unusable SYMLINK is created.
Even if I create the target directory later, the symlink is unusable from the Windows explorer.

A more accurate issue, git-for-windows/git issue 1646 is supposed to be fixed in 2.17+.
Still, the OP Jozef has created a new issue: git-for-windows/git issue 2177.

The maintainer Johannes Schindelin (github.com/dscho) just added:

Git's internal data model unfortunately has a very Unix-centric view of symbolic links.
In Git for Windows, we work around that by trying to determine the type from the target (if it exists). That heuristic breaks in your case.

However, we recently introduced the feature where you can declare the symlink type in the .gitattributes: simply add a line to that file (or create that file with this line as contents if it does not yet exist):

my_symlink_name symlink=dir

Of course, you will want to add and commit this file.

I was modeling the .gitattributes line after this information:

mklink /d symlink ..<some non-existing directory><another non-existing directory>

The first column in .gitattributes is always a file name or file name pattern

The OP confirms:

That works great on a Windows 10 box running Git 2.21 (had to create 5 .gitattributes files)
It does not work on a Windows 7 box running Git 2.17.

Johannes points out to Git for Windows v2.19.1 (Oct 5th 2018)

The type of symlinks to create (directory or file) can now be specified via the .gitattributes.

See commit 25a7f44.

Symlinks:

On Windows, symbolic links have a type: a "file symlink" must point at a file, and a "directory symlink" must point at a directory. If the type of symlink does not match its target, it doesn't work.

Git does not record the type of symlink in the index or in a tree.
On checkout it'll guess the type, which only works if the target exists at the time the symlink is created. This may often not be the case, for example when the link points at a directory inside a submodule.

The symlink attribute allows you to explicitly set the type of symlink to file or dir, so Git doesn't have to guess.

If you have a set of symlinks that point at other files, you can do:

------------------------
*.gif   symlink=file
------------------------

To tell Git that a symlink points at a directory, use:

------------------------
tools_folder    symlink=dir
------------------------

The symlink attribute is ignored on platforms other than Windows, since they don't distinguish between different types of symlinks.

Upvotes: 5

Related Questions