-
-
Notifications
You must be signed in to change notification settings - Fork 312
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Checking out a dangling symlink on Windows is treated as a hard error #1354
Comments
Thanks a lot for reporting! I also think that it appears reasonable to start out Git-compatible and fallback to creating a dangling |
Git silently falls back to creating a file symlink, does not report that anything of note occurred, and exits indicating success (i.e. with a status of 0).
That demonstration is on Windows 10, using PowerShell 7.4.2. The wording of the error message from In contrast, the wording of the external
There's a nuance here that I didn't cover in the issue description (and still haven't). I think it is really not Windows, per se, to which the distinction between file and directory symlinks apples, but rather the NTFS filesystem. Windows can access other filesystems that do not have this distinction, and other operating systems can access NTFS (and have had stable read-write drivers for NTFS for a long time, so often do). A common case of accessing NTFS from a non-Windows system, even though it is not always a best practice, would be using Git or related tools (thus presumably people do, and will, also do this with However, some operations do actually work in NTFS in Windows through a file symlink to a directory, while others do not. I am unsure if any operations will actually fail through a file symlink to a directory on any other operating systems accessing NTFS, even in the case where it is the host Windows filesystem accessed through drive mounts under To illustrate this broadly, in a way that does not even bring in WSL or anything but native Windows, consider the following, which is still just in PowerShell on Windows: git -c core.symlinks=true clone git@github.com:EliahKagan/has-dangling-symlink.git
mkdir has-dangling-symlink/target
echo 'Contents of file inside symlink target directory.' >has-dangling-symlink/target/inside These commands all successfully display content of cat has-dangling-symlink/target/inside
cat has-dangling-symlink/symlink/inside
cat has-dangling-symlink\target\inside
cat has-dangling-symlink\symlink\inside These commands both successfully open notepad has-dangling-symlink/target/inside
notepad has-dangling-symlink\target\inside Neither of these commands open notepad has-dangling-symlink/symlink/inside
notepad has-dangling-symlink\target\inside In contrast, renaming notepad has-dangling-symlink/symlink/inside.txt
notepad has-dangling-symlink\symlink\inside.txt Presumably the relevant difference is in Notepad's approach to checking if a file exists with the name given, before deciding to try with a (In all of the above, the purpose of the Once the directory target exists, the symlink can be fixed by removing it and having Git check it out again. One way to verify it really is currently a file symlink is to run: cmd /c dir has-dangling-symlink The resulting listing includes a row like this, with
Then delete the symlink and have Git check it out again, and check it again: rm has-dangling-symlink/symlink
git -C has-dangling-symlink restore symlink
cmd /c dir has-dangling-symlink The resulting listing includes a row like this, now with
And all four Maybe Git has some way to automatically perform those replacements without opening or deleting any other files unnecessarily, but I don't know of it. That might be a good thing to provide, if it could be done correctly. NTFS has other kinds of reparse points besides symlinks, such as junctions, which is typically unimportant in the context of Git because Git does not represent them and will not create them, and because users who create them tend to be aware of their implications.* But a tool that checks to ensure symlinks are of the correct type and applies automatic fixes depending on what is going on at their targets should possibly be aware of them, at least to ensure no unexpected recursive deletions occur (junctions are almost always dereferenced). But for improving the default behavior described in this issue, I don't believe any kinds of NTFS reparse points besides file symlinks and directory symlinks have to be considered. In case it is ever helpful, this item in the Cygwin FAQ gives some insight into the complexity of symbolic links on Windows, a complexity that remains in significant part even when the Cygwin-specific parts are disregarded. (As I understand it, Rust doesn't target Cygwin at this time, not even experimentally; so the Cygwin parts shouldn't come into play here.) * Edit: I had previously claimed that junctions were among the kinds of reparse points that usually require special privileges to create, which is in accurate. Limited user accounts on Windows can create junctions. See #1354 (comment). |
…1354) This behaviour is the same as in Git.
…1354) This behaviour is the same as in Git.
…1354) This behaviour is the same as in Git.
With commit 78dedeb the problem is definitely fixed locally, turning the test from red to green. However, on CI, I have never seen failure which must mean that the filesystem probe doesn't think that symlinks are supported (Edit: it's probably because it's currently implemented in a way that is not thread-safe, but with the fix the probe can actually be simplified (while still not safe for concurrent operations)). |
…1354) This behaviour is the same as in Git.
…1354) This behaviour is the same as in Git.
…1354) This behaviour is the same as in Git.
…1354) This behaviour is the same as in Git.
In #1354 (comment), I had said:
Although much of that is accurate, the parenthesized portion is very much inaccurate with respect to junctions, which limited user accounts on Windows can create. Sorry about that! I've edited the comment to avoid further misinforming those who read it. I noticed the above error when looking into whether gitoxide treats NTFS junctions in a reasonable way when it encounters them in working trees, including in If on deeper examination I encounter behavior around junctions in gitoxide that looks like a bug, then I'll open a separate issue for that. |
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: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:
When
core.symlinks
isfalse
, 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:
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./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 whencore.symlinks
is set tofalse
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.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 settingcore.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: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
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 tofalse
, as is often the case on Windows, there is no error. To check that this is the case, first verify that there is still nohas-dangling-symlink
directory, then run a command like the abovegix clone
command but withfalse
instead oftrue
:This succeeds, and (since
core.symlinks
isfalse
) 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 fromgit
.)First delete the
has-dangling-symlink
directory that was created in the previous step:rm -r -fo has-dangling-symlink
rm -rf has-dangling-symlink
Now run a command like the original clone command, but with
gix
instead ofgit
: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:
This should show that
symlink -> target
while also not listing any filetarget
.The text was updated successfully, but these errors were encountered: