Description
Current behavior 😯
A repository may contain a dangling (i.e., broken) symbolic link. On Windows, when such a repository's working tree is checked out, such as in the checkout that occurs in a clone done with gix clone
, this causes the entire checkout to fail. gix clone
reports:
Error: IO error while writing blob or reading file metadata or changing filetype
Caused by:
The system cannot find the file specified. (os error 2)
The cause appears to be that the symbolic link's target is not found, due to not existing (or otherwise being in a location where the user running gix
cannot list its metadata).
On Windows, file and directory symlinks are two separate kinds of symlinks, which different metadata in the symlink itself. Therefore, on Windows, both Git and gitoxide check what kind of file the target is, so that, for symlinks that are not broken, they are created as the correct kind of symlink for the target. However, whereas Git falls back to creating file symlinks when the target is missing (as if the target were a regular file), gitoxide treats this as a hard error.
Expected behavior 🤔
Actually, I am not sure what the best thing to do is. I am inclined to say that we should follow the Git behavior here, but really the main problem is that the difference is unexpected. Therefore, one of the following approaches should be taken:
- Fall back to creating a file symlink, when the target cannot be found, as Git does.
- Keep failing, but report a specific error. Also document this behavior prominently.
- Keep failing, but only for that specific file; have the checkout otherwise proceed, failing with an error status and specific error messages for any non-created symlinks, but creating the other files. Also document this behavior prominently.
- Fall back to creating a file symlink, but also report a warning about it, in case that is not what the user wants.
- Some combination of the above, by having it be configurable (and, at least if the default behavior does not match that of Git, then documenting it prominently).
When core.symlinks
is false
, there is no failure, since no attempt is made to create symbolic links. So in that case nothing needs to be done differently for this. (Note that this issue is, as far as I can tell, entirely independent of #1353, and the steps to reproduce given below make sure to avoid the effect of #1353.)
It seems to me that there are substantial advantages of being able to create a dangling symlink when cloning, though also some disadvantages. These are the cases I am aware of:
- The symlink may be broken due to a bug in the repository, but if
core.symlinks
is set totrue
then the user probably prefers to work on that problem with the actual dangling symlink, and may especially prefer that the other files be checked out. - The symlink may be broken due to an unintentionally missing file outside the working tree, or inside the working tree (ignored or not). It could be fixed by putting the target file in place, though on Windows the target would have to be a regular file for this to work (without recreating the symlink).
- The symlink may be broken due to an intentionally missing file, intended to be created after cloning. This is the same as the previous case, except that creating the dangling symlink is likely to be strongly preferred, at least if it will be possible to dereference properly once the file exists (as would be the case with Git's default, if it is a regular file).
- The symlink is intended only to work on Unix-like systems. For example, maybe it's a symlink to something in
/etc
. But it should at least be possible to check out the repository on Windows. In this case, it doesn't matter what happens with the symlink itself.
Git behavior
Git creates a file symbolic link if it cannot figure out what the target is supposed to be.
Although that behavior seems good, the absence of any outwardly observable special treatment for this case is arguably not ideal. Git does not warn or otherwise print any message to indicate that this happened, and the clone reports success, i.e. exit status 0.
Commands such as git status
do not show that anything is amiss, though that is arguably ideal, at least in the absence of other design changes, given such commands also do not show that anything special is going on when core.symlinks
is set to false
and regular files are created in their place instead.
Steps to reproduce 🕹
Create a repository with a dangling symlink
This can be done on a Unix-like system or Windows. On Windows, if you have trouble, you can create a symlink to an actual file, then delete the target. As in #1353, I created the repository on Ubuntu and uploaded it to a SSH remote to ensure that the problem was not specific to file://
remotes.
git init has-dangling-symlink
cd has-dangling-symlink
ln -s target symlink
git add symlink
git commit -m 'Initial commit'
Or use the has-dangling-symlink repository, which is already set up. (The rest of these instructions are written assuming that is used.)
The remaining steps are to be done on Windows, since this is a Windows-specific bug. It should also be done with a user account capable of creating symbolic links; at minimum, doing it in an account that cannot do this would not decisively show the bug. However, no global or other Git configuration related to symbolic links is required, since -c
is used below in setting core.symlinks
to true.
Check that the clone should succeed
On Windows, in PowerShell or Git Bash, ensure you have no has-dangling-symlink
directory by running this command and checking that it gives an expected error:
ls has-dangling-symlink
That is important since the effect of attempting to clone to an already-populated location could potentially be confused with the situations being shown here.
Attempt to clone with gix
gix clone -c core.symlinks=true git@github.com:EliahKagan/has-dangling-symlink.git
This fails, with an error message as shown above, and the has-dangling-symlink
directory is not kept.
Verify that it works when it need not create symlinks
When core.symlinks
is set to false
, as is often the case on Windows, there is no error. To check that this is the case, first verify that there is still no has-dangling-symlink
directory, then run a command like the above gix clone
command but with false
instead of true
:
gix clone -c core.symlinks=false git@github.com:EliahKagan/has-dangling-symlink.git
This succeeds, and (since core.symlinks
is false
) a regular file is created in place of the symlink.
Optionally, compare to Git
This may be a good idea to do, especially if there is any doubt that the test setup is one where the user is capable of creating symlinks. (It is also part of reproducing this bug in the sense that it verifies that this really is a way that gix
behaves differently from git
.)
First delete the has-dangling-symlink
directory that was created in the previous step:
- In PowerShell:
rm -r -fo has-dangling-symlink
- In Git Bash:
rm -rf has-dangling-symlink
Now run a command like the original clone command, but with gix
instead of git
:
git -c core.symlinks=true clone git@github.com:EliahKagan/has-dangling-symlink.git
This should succeed and create the dangling symbolic link. That it is a symbolic link, and that its target is a file that is not present, can be verified by running:
ls -l has-dangling-symlink
This should show that symlink -> target
while also not listing any file target
.