Skip to content
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

git mv on a symlink dereferences it and renames the target in 2.48.1 #5436

Closed
EliahKagan opened this issue Feb 21, 2025 · 0 comments · Fixed by #5437
Closed

git mv on a symlink dereferences it and renames the target in 2.48.1 #5436

EliahKagan opened this issue Feb 21, 2025 · 0 comments · Fixed by #5437
Milestone

Comments

@EliahKagan
Copy link

Summary

Running git mv symlink symlink-renamed where symlink is a symbolic link that points to file is supposed to rename symlink to symlink-renamed and leave file alone. But starting in Git for Windows 2.48.1, it instead leaves symlink unchanged and renames file to symlink-renamed. This only happens on Windows. I have verified this happens on Windows 10 and Windows 11 and that it does not depend on how Git for Windows is installed.

Fortunately, this only affects operations where rename() is called. This is not limited to git mv, but that seems to be the only operation where it is likely to occur on files in a working tree. As far as I can tell, both through experimentation and by examining the code and searching for uses of rename, this is not triggered by operations such as switching branches.

The cause

The problem happens because of the new mingw_rename() implementation merged in 183ea3e, which needs FILE_FLAG_OPEN_REPARSE_POINT as one of the flags in the dwFlagsAndAttributes argument in order for CreateFileW to open the symlink rather than the target file, but omits that flag:

git/compat/mingw.c

Lines 2927 to 2929 in 2bd190b

old_handle = CreateFileW(wpold, DELETE,
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);

Verification and proposed fix

I have verified that this change to compat/mingw.c fixes the regression:

@@ -2926,7 +2926,9 @@ int mingw_rename(const char *pold, const char *pnew)

                old_handle = CreateFileW(wpold, DELETE,
                                         FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
-                                        NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL);
+                                        NULL, OPEN_EXISTING,
+                                        FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT,
+                                        NULL);
                if (old_handle == INVALID_HANDLE_VALUE) {
                        errno = err_win_to_posix(GetLastError());
                        return -1;

The fix is very simple, so I'm not sure how useful it would be for me to submit a patch, but I'll open a pull request offering a commit with the change with what I hope to be a suitable commit message.

(Ordinarily a patch would include new tests, but I am unsure if that is needed here. As detailed below, this regression does cause existing tests to fail when they are run in a way that permits the user, git, and ln to create symlinks--and I suspect any new tests would overlap significantly with existing ones and have similar preconditions.)

Steps to reproduce

I first observed this this on Windows 10 in a 64-bit git installation managed by scoop, initially when running test fixture script in the gitoxide test suite that use git (GitoxideLabs/gitoxide#1849), then later interactively with the simplified instructions given below (not involving gitoxide or any extra complexity).

To ensure my environment was not the cause, I tested it on Windows 11 with 64-bit PortableGit, also with versions 2.47.1(2) and 2.48.1, verifying that only 2.48.1 was affected. Then I tested it again on both the Windows 10 and Windows 11 systems with the Git for Windows SDK to compare the version installed by sdk cd git (which is currently the same commit as 2.48.1) and the ameliorating effect of adding the FILE_FLAG_OPEN_REPARSE_POINT flag as shown in the above diff.

On both systems, I enabled Developer Mode to allow all users to create symlinks (and to do so without UAC elevation), and set core.symlinks to true via git config --global core.symlinks true.

Because Windows distinguishes file and directory symlinks, as well as to ensure my proposed fix does not break the improvements of b30404d, I tested with two repositories: one with a symlink to a regular file, and another with a symlink to a directory. The following is from PortableGit 2.48.1 on the Windows 11 system, in Git Bash, testing with the simple has-symlink repository:

ek@DESKTOP-0L1QG1Q MINGW64 ~/src
$ git clone https://github.com/EliahKagan/has-symlink.git                       Cloning into 'has-symlink'...
remote: Enumerating objects: 8, done.
remote: Counting objects: 100% (8/8), done.
remote: Compressing objects: 100% (6/6), done.
remote: Total 8 (delta 0), reused 8 (delta 0), pack-reused 0 (from 0)
Receiving objects: 100% (8/8), done.

ek@DESKTOP-0L1QG1Q MINGW64 ~/src
$ cd has-symlink/

ek@DESKTOP-0L1QG1Q MINGW64 ~/src/has-symlink (main)
$ ls -l
total 3
-rw-r--r-- 1 ek 197121 617 Feb 21 00:42 COPYING
-rw-r--r-- 1 ek 197121 200 Feb 21 00:42 README.md
lrwxrwxrwx 1 ek 197121   6 Feb 21 00:42 symlink -> target
-rw-r--r-- 1 ek 197121  25 Feb 21 00:42 target

ek@DESKTOP-0L1QG1Q MINGW64 ~/src/has-symlink (main)
$ cmd //c dir
 Volume in drive C has no label.
 Volume Serial Number is E039-3F46

 Directory of C:\Users\ek\src\has-symlink

02/21/2025  12:42 AM    <DIR>          .
02/21/2025  12:42 AM    <DIR>          ..
02/21/2025  12:42 AM               617 COPYING
02/21/2025  12:42 AM               200 README.md
02/21/2025  12:42 AM    <SYMLINK>      symlink [target]
02/21/2025  12:42 AM                25 target
               4 File(s)            842 bytes
               2 Dir(s)  220,282,855,424 bytes free

ek@DESKTOP-0L1QG1Q MINGW64 ~/src/has-symlink (main)
$ git status
On branch main
Your branch is up to date with 'origin/main'.

nothing to commit, working tree clean

ek@DESKTOP-0L1QG1Q MINGW64 ~/src/has-symlink (main)
$ git mv symlink symlink-renamed

ek@DESKTOP-0L1QG1Q MINGW64 ~/src/has-symlink (main)
$ ls -l
total 3
-rw-r--r-- 1 ek 197121 617 Feb 21 00:42 COPYING
-rw-r--r-- 1 ek 197121 200 Feb 21 00:42 README.md
lrwxrwxrwx 1 ek 197121   6 Feb 21 00:42 symlink -> target
-rw-r--r-- 1 ek 197121  25 Feb 21 00:42 symlink-renamed

ek@DESKTOP-0L1QG1Q MINGW64 ~/src/has-symlink (main)
$ cmd //c dir
 Volume in drive C has no label.
 Volume Serial Number is E039-3F46

 Directory of C:\Users\ek\src\has-symlink

02/21/2025  12:43 AM    <DIR>          .
02/21/2025  12:42 AM    <DIR>          ..
02/21/2025  12:42 AM               617 COPYING
02/21/2025  12:42 AM               200 README.md
02/21/2025  12:42 AM    <SYMLINK>      symlink [target]
02/21/2025  12:42 AM                25 symlink-renamed
               4 File(s)            842 bytes
               2 Dir(s)  220,282,855,424 bytes free

ek@DESKTOP-0L1QG1Q MINGW64 ~/src/has-symlink (main)
$ git status
On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
        renamed:    symlink -> symlink-renamed

Changes not staged for commit:
  (use "git add/rm <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
        typechange: symlink-renamed
        deleted:    target

Untracked files:
  (use "git add <file>..." to include in what will be committed)
        symlink

I observed the same effect on a repository with a directory symlink, has-symlink-to-directory. All tested versions, including the affected version 2.48.1, cloned these repositories without problems and checked out the symlinks. Only renaming/moving is affected.

Reproduction via the test suite

Provided the user who runs the t7001-mv.sh tests has the ability to create symlinks (and it is not filtered out via UAC), and provided MSYS is set to a value that causes ln -s to create actual symlinks (such as winsymlinks:nativestrict), two of the tests in that file fail when the regression is present, then pass under the change described above:

not ok 38 - git mv should overwrite file with a symlink
#
#               rm -fr .git &&
#               git init &&
#               echo 1 >moved &&
#               test_ln_s_add moved symlink &&
#               git add moved &&
#               test_must_fail git mv symlink moved &&
#               git mv -f symlink moved &&
#               test_path_is_missing symlink &&
#               git update-index --refresh &&
#               git diff-files --quiet
#
not ok 39 - check moved symlink
#
#               test_path_is_symlink moved
#

System details

The usual requested information about the environment is as follows, for the Windows 11 system and running the commands in Git Bash provided by PortableGit 2.48.1:

$ git --version --build-options
git version 2.48.1.windows.1
cpu: x86_64
built from commit: 2bd190bcd280bc95f537c2d532880a5e539b5132
sizeof-long: 4
sizeof-size_t: 8
shell-path: D:/git-sdk-64-build-installers/usr/bin/sh
feature: fsmonitor--daemon
libcurl: 8.12.1
OpenSSL: OpenSSL 3.2.4 11 Feb 2025
zlib: 1.3.1
$ cmd //c ver
Microsoft Windows [Version 10.0.26100.3194]

While /etc/install-options.txt doesn't seem to be available anymore, some of that information, as well as other details, are shown by this git config command:

$ git config -l --show-scope
system  core.symlinks=false
system  core.autocrlf=true
system  core.fscache=true
system  color.interactive=true
system  color.ui=auto
system  help.format=html
system  diff.astextplain.textconv=astextplain
system  rebase.autosquash=true
system  filter.lfs.clean=git-lfs clean -- %f
system  filter.lfs.smudge=git-lfs smudge -- %f
system  filter.lfs.process=git-lfs filter-process
system  filter.lfs.required=true
system  credential.helper=helper-selector
global  core.symlinks=true
EliahKagan added a commit to EliahKagan/git that referenced this issue Feb 21, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: git-for-windows@183ea3e
[2]: git-for-windows@b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: git-for-windows#5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
dscho pushed a commit to dscho/git that referenced this issue Feb 21, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: git-for-windows@183ea3e
[2]: git-for-windows@b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: git-for-windows#5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
git-for-windows-ci pushed a commit that referenced this issue Feb 21, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
git-for-windows-ci pushed a commit that referenced this issue Feb 21, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
@dscho dscho added this to the Next release milestone Feb 21, 2025
gitster pushed a commit to git/git that referenced this issue Feb 22, 2025
Since 183ea3e (Merge branch 'ps/mingw-rename', 2024-11-13),
a new technique is used on Windows to rename files, where supported.
The first step of this technique is to open the file with
`CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: git-for-windows@183ea3e
[2]: git-for-windows@b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: git-for-windows#5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
git-for-windows-ci pushed a commit that referenced this issue Feb 22, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
dscho pushed a commit that referenced this issue Feb 25, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
dscho pushed a commit that referenced this issue Feb 25, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
git-for-windows-ci pushed a commit that referenced this issue Feb 25, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
git-for-windows-ci pushed a commit that referenced this issue Feb 26, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
dscho pushed a commit that referenced this issue Feb 26, 2025
Since 183ea3e [1], a new technique is used on Windows to rename files,
where supported. The first step of this technique is to open the file
with `CreateFileW`. At that time, `FILE_ATTRIBUTE_NORMAL` was passed as
the value of the `dwFlagsAndAttributes` argument. In b30404d [2], this
was improved by passing `FILE_FLAG_BACKUP_SEMANTICS`, to support
directories as well as regular files.

However, neither value of `dwFlagsAndAttributes` is sufficient to open
a symbolic link with the correct semantics to rename it. Symlinks on
Windows are reparse points. Attempting to open a reparse point with
`CreateFileW` dereferences the reparse point and opens the target
instead, unless `FILE_FLAG_OPEN_REPARSE_POINT` is included in
`dwFlagsAndAttributes`. This is documented for that flag and in the
"Symbolic Link Behavior" section of the `CreateFileW` docs [3].

This produces a regression where attempting to rename a symlink on
Windows renames its target to the intended new name and location of the
symlink. For example, if `symlink` points to `file`, then running

    git mv symlink symlink-renamed

leaves `symlink` in place and unchanged, but renames `file` to
`symlink-renamed` [4].

This regression is detectable by existing tests in `t7001-mv.sh`, but
the tests must be run by a Windows user with the ability to create
symlinks, and the `ln -s` command used to create the initial symlink
must also be able to create a real symlink (such as by setting the
`MSYS` environment variable to `winsymlinks:nativestrict`). Then
these two tests fail if the regression is present, and pass otherwise:

    38 - git mv should overwrite file with a symlink
    39 - check moved symlink

Let's fix this, so that renaming a symlink again renames the symlink
itself and leaves the target unchanged, by passing

    FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OPEN_REPARSE_POINT

as the `dwFlagsAndAttributes` argument. This is sufficient (and safe)
because including `FILE_FLAG_OPEN_REPARSE_POINT` causes no harm even
when used to open a file or directory that is not a reparse point. In
that case, as noted in [3], this flag is simply ignored.

[1]: 183ea3e
[2]: b30404d
[3]: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-createfilew
[4]: #5436

Signed-off-by: Eliah Kagan <eliah.kagan@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants