Description
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