Skip to content

Checking out a dangling symlink on Windows is treated as a hard error #1354

Closed
@EliahKagan

Description

@EliahKagan

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 to true 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    C-WindowsWindows-specific issuesacknowledgedan issue is accepted as shortcoming to be fixedhelp wantedExtra attention is needed

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions