Current behavior 😯
On Windows, core.symlinks is intended to to false when not set, and in some installations is even set as false in the installation scope. Changing it, if done, is most often done in the global scope, such as by running git config --global core.symlinks true.
But gix clone never honors this global configuration.
Prior to gitoxide 0.36.0, it checked out regular-file stubs rather than symlinks, even on a Windows system where the user running it is capable of creating symlinks in the directory being checked out to, even if core.symlinks was globally set to true. It furthermore would set core.symlinks to false explicitly in the newly cloned repository's .git/config.
Starting in gitoxide 0.36.0, the behavior has changed, but the bug of disregarding core.symlinks in the global scope has remained. The current behavior is to check out symlinks as symlinks on a Windows system where the user running it is capable of creating symlinks in the directory being checked out to, even when core.symlinks is globally set to false.
(This also now happens when core.symlinks is globally unset and not otherwise specified, but even though that differs from the Git behavior, it is not itself obviously a bug or undesirable.)
gix does honor core.symlinks in the command scope, both with -c core.symlinks=true and with GIT_{CONFIG_KEY_VALUE}. But even in that case, gix clone sets a value in the local scope (in the reposiitory's .git/config) reflecting its determination of whether symlinks are supported.
This is not readily checkable on CI without tests specifically for it or other special effort, since the Windows runners for GitHub Actions customize core.symlinks, setting it to true for the installation, at installation time. This coincides with the behavior gix detects is available, in in any case technically involves the installation or system scope rather than the global scope.
Expected behavior 🤔
When core.symlinks is set to true explicitly in the global scope and it has not been overridden in the command scope, gix clone should attempt to check out symbolic links as symbolic links rather than as regular files. I believe it will still not do this as often as it should, though in most cases where it is desired that does now happen, since it creates symlinks when it detects it can.
When core.symlinks is set to false explicitly in the global scope and it has not been overridden in the command scope, gix clone should not attempt to check out symbolic links as symbolic links, but should instead create regular files. This is the expected behavior that, currently, is decisively not satisfied: the global configuration scope cannot currently be used to cause gix clone not to check out symlinks.
Git behavior
Git will check out symlinks as actual Windows symbolic links rather than as regular-file stubs, including in commands such as git clone.
Git also does not set this variable in the local scope. Local repository .git/config are not created with symlinks set in their [core] sections.
Steps to reproduce 🕹
Make the test repository
Create a repository with a symbolic link. For simplicity, it should be a symbolic link to a regular file in the repository, and thus not dangling/broken. (Besides simplicity, this also avoids confusion relating to the separate issue #1354.) This can be done with Git either on Windows or on a Unix-like system; I used Ubuntu, pushed to a remote, and cloned from a remote, to ensure that this bug was not specific to file:// remotes.
git init has-symlink
cd has-symlink
echo 'This is a regular file.' >target
ln -s target symlink
git commit -m 'Initial commit'
Alternatively, the has-symlink repository may be used. It was created that way (then minimal documentation was added in a second commit). It is used in the instructions below, which assume that either PowerShell or Git Bash is used.
Set core.symlinks to false globally and/or check that it is set
On a Windows system, run this command, at least if that has not already been done in your user account:
git config --global core.symlinks false
Checking that it is set in the global scope by running git config core.symlinks should show false.
Clone with gix clone and inspect the repository
On a Windows system in which you have the ability to create symlinks, in a location on a volume that supports them, clone the repository with gix:
gix clone git@github.com:EliahKagan/has-symlink.git
The clone succeeds, but inspecting the checked out files reveals that symlink is a symbolic link, even though it should have been created as a regular file due to the global core.symlinks value of false taken together with the absence of any other more narrowly scoped configuration that would override it.
ls -l has-symlink/symlink
In PowerShell, that shows output like:
Directory: C:\Users\ek\src\has-symlink
Mode LastWriteTime Length Name
---- ------------- ------ ----
la--- 6/23/2024 1:31 AM 0 symlink -> target
To verify that gix set core.symlinks to true in the local configuration, inspect has-symlink/.git/config, or run this command, which outputs true:
git -C has-symlink config --local core.symlinks
Redo the clone with core.symlinks set to false in the command
To see how this differs from the behavior of gix when core.symlinks is provided on the command line, first delete the cloned repository:
- In Git Bash, run:
rm -rf has-symlink
- In PowerShell, run:
rm -r -fo has-symlink
Then try the clone again, but this time use the -c option to gix:
gix -c core.symlinks=false clone git@github.com:EliahKagan/has-symlink.git
Check the symlink entry again:
ls -l has-symlink/symlink
This time, that shows that it is a regular file on disk. For example, in PowerShell, it looks like:
Directory: C:\Users\ek\src\has-symlink
Mode LastWriteTime Length Name
---- ------------- ------ ----
-a--- 6/23/2024 1:04 PM 6 symlink
However, it has still set core.symlinks to true in the newly cloned repository's local configuration. This still outputs false:
git -C has-symlink config --local core.symlinks
Thus the reason -c behaved differently was that it overrode the wrongly set false value that gix placed at repository level. Furthermore, using -c is an insufficient workaround because it is still set otherwise locally (though unsetting it locally after cloning it that way should be a sufficient workaround until a fix is available).
Optionally, compare to the behavior of Git
Deleting the cloned repository again and cloning it with git clone rather than gix clone shows the expected behavior, and -c does not need to be used.
The behavior of Git may go further than desired for gitoxide, however, since it defaults core.symlinks to false on Windows even when it is unset and the user is capable of creating symbolic links. My guess is that, when core.symlinks is unset in all scopes originally, then operating based on capabilities may be the intended behavior of gitoxide.
The key behavior of Git that gitoxide should but does not follow is that an explicit core.symlinks setting should be followed, at least whenever that is possible to do. Following an explicit value of false for core.symlinks is always possible to do.
Optionally, verify that other global configuration is honored
The problem is not that gix doesn't use global configuration in general. I verified this by temporarily breaking ssh for git and gix by running:
git config --global core.sshCommand foo # Warning: breaks ssh in git until undone!
Then I attempted to clone a repository that I know I can otherwise clone with gix:
gix clone git@github.com:EliahKagan/gitoxide.git
As expected, this attempted to use the bogus foo implementation of SSH:
Error: Failed to invoke program "foo"
Caused by:
program not found
To undo that, simply run:
git config --global --unset core.sshCommand
Current behavior 😯
On Windows,
core.symlinksis intended to tofalsewhen not set, and in some installations is even set asfalsein the installation scope. Changing it, if done, is most often done in the global scope, such as by runninggit config --global core.symlinks true.But
gix clonenever honors this global configuration.Prior to
gitoxide0.36.0, it checked out regular-file stubs rather than symlinks, even on a Windows system where the user running it is capable of creating symlinks in the directory being checked out to, even ifcore.symlinkswas globally set totrue. It furthermore would setcore.symlinkstofalseexplicitly in the newly cloned repository's.git/config.Starting in
gitoxide0.36.0, the behavior has changed, but the bug of disregardingcore.symlinksin the global scope has remained. The current behavior is to check out symlinks as symlinks on a Windows system where the user running it is capable of creating symlinks in the directory being checked out to, even whencore.symlinksis globally set tofalse.(This also now happens when
core.symlinksis globally unset and not otherwise specified, but even though that differs from the Git behavior, it is not itself obviously a bug or undesirable.)gixdoes honorcore.symlinksin the command scope, both with-c core.symlinks=trueand withGIT_{CONFIG_KEY_VALUE}. But even in that case,gix clonesets a value in the local scope (in the reposiitory's.git/config) reflecting its determination of whether symlinks are supported.This is not readily checkable on CI without tests specifically for it or other special effort, since the Windows runners for GitHub Actions customize
core.symlinks, setting it totruefor the installation, at installation time. This coincides with the behaviorgixdetects is available, in in any case technically involves the installation or system scope rather than the global scope.Expected behavior 🤔
When
core.symlinksis set totrueexplicitly in the global scope and it has not been overridden in the command scope,gix cloneshould attempt to check out symbolic links as symbolic links rather than as regular files. I believe it will still not do this as often as it should, though in most cases where it is desired that does now happen, since it creates symlinks when it detects it can.When
core.symlinksis set tofalseexplicitly in the global scope and it has not been overridden in the command scope,gix cloneshould not attempt to check out symbolic links as symbolic links, but should instead create regular files. This is the expected behavior that, currently, is decisively not satisfied: the global configuration scope cannot currently be used to causegix clonenot to check out symlinks.Git behavior
Git will check out symlinks as actual Windows symbolic links rather than as regular-file stubs, including in commands such as
git clone.Git also does not set this variable in the local scope. Local repository
.git/configare not created withsymlinksset in their[core]sections.Steps to reproduce 🕹
Make the test repository
Create a repository with a symbolic link. For simplicity, it should be a symbolic link to a regular file in the repository, and thus not dangling/broken. (Besides simplicity, this also avoids confusion relating to the separate issue #1354.) This can be done with Git either on Windows or on a Unix-like system; I used Ubuntu, pushed to a remote, and cloned from a remote, to ensure that this bug was not specific to
file://remotes.Alternatively, the has-symlink repository may be used. It was created that way (then minimal documentation was added in a second commit). It is used in the instructions below, which assume that either PowerShell or Git Bash is used.
Set
core.symlinkstofalseglobally and/or check that it is setOn a Windows system, run this command, at least if that has not already been done in your user account:
git config --global core.symlinks falseChecking that it is set in the global scope by running
git config core.symlinksshould showfalse.Clone with
gix cloneand inspect the repositoryOn a Windows system in which you have the ability to create symlinks, in a location on a volume that supports them, clone the repository with
gix:The clone succeeds, but inspecting the checked out files reveals that
symlinkis a symbolic link, even though it should have been created as a regular file due to the globalcore.symlinksvalue offalsetaken together with the absence of any other more narrowly scoped configuration that would override it.In PowerShell, that shows output like:
To verify that
gixsetcore.symlinkstotruein the local configuration, inspecthas-symlink/.git/config, or run this command, which outputstrue:Redo the clone with
core.symlinksset tofalsein the commandTo see how this differs from the behavior of
gixwhencore.symlinksis provided on the command line, first delete the cloned repository:rm -rf has-symlinkrm -r -fo has-symlinkThen try the clone again, but this time use the
-coption togix:Check the
symlinkentry again:This time, that shows that it is a regular file on disk. For example, in PowerShell, it looks like:
However, it has still set
core.symlinkstotruein the newly cloned repository's local configuration. This still outputsfalse:Thus the reason
-cbehaved differently was that it overrode the wrongly setfalsevalue thatgixplaced at repository level. Furthermore, using-cis an insufficient workaround because it is still set otherwise locally (though unsetting it locally after cloning it that way should be a sufficient workaround until a fix is available).Optionally, compare to the behavior of Git
Deleting the cloned repository again and cloning it with
git clonerather thangix cloneshows the expected behavior, and-cdoes not need to be used.The behavior of Git may go further than desired for gitoxide, however, since it defaults
core.symlinkstofalseon Windows even when it is unset and the user is capable of creating symbolic links. My guess is that, whencore.symlinksis unset in all scopes originally, then operating based on capabilities may be the intended behavior of gitoxide.The key behavior of Git that gitoxide should but does not follow is that an explicit
core.symlinkssetting should be followed, at least whenever that is possible to do. Following an explicit value offalseforcore.symlinksis always possible to do.Optionally, verify that other global configuration is honored
The problem is not that
gixdoesn't use global configuration in general. I verified this by temporarily breakingsshforgitandgixby running:Then I attempted to clone a repository that I know I can otherwise clone with
gix:As expected, this attempted to use the bogus
fooimplementation of SSH:To undo that, simply run: