]> git.ipfire.org Git - thirdparty/git.git/commitdiff
mingw: support renaming symlinks
authorKarsten Blees <blees@dcon.de>
Wed, 17 Dec 2025 14:08:48 +0000 (14:08 +0000)
committerJunio C Hamano <gitster@pobox.com>
Wed, 17 Dec 2025 23:22:19 +0000 (08:22 +0900)
Older MSVCRT's `_wrename()` function cannot rename symlinks over
existing files: it returns success without doing anything. Newer
MSVCR*.dll versions probably do not share this problem: according to CRT
sources, they just call `MoveFileEx()` with the `MOVEFILE_COPY_ALLOWED`
flag.

Avoid the `_wrename()` call, and go with directly calling
`MoveFileEx()`, with proper error handling of course.

Signed-off-by: Karsten Blees <blees@dcon.de>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
compat/mingw.c

index b1cc30d0f13922291eea8215d667fba851e5efc2..55f0bb478e61271ee994dcfc233e600c23a754a7 100644 (file)
@@ -2275,7 +2275,7 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
 int mingw_rename(const char *pold, const char *pnew)
 {
        static int supports_file_rename_info_ex = 1;
-       DWORD attrs, gle;
+       DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
        int tries = 0;
        wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
        int wpnew_len;
@@ -2286,15 +2286,6 @@ int mingw_rename(const char *pold, const char *pnew)
        if (wpnew_len < 0)
                return -1;
 
-       /*
-        * Try native rename() first to get errno right.
-        * It is based on MoveFile(), which cannot overwrite existing files.
-        */
-       if (!_wrename(wpold, wpnew))
-               return 0;
-       if (errno != EEXIST)
-               return -1;
-
 repeat:
        if (supports_file_rename_info_ex) {
                /*
@@ -2370,13 +2361,22 @@ repeat:
                 * to retry.
                 */
        } else {
-               if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
+               if (MoveFileExW(wpold, wpnew,
+                               MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
                        return 0;
                gle = GetLastError();
        }
 
-       /* TODO: translate more errors */
-       if (gle == ERROR_ACCESS_DENIED &&
+       /* revert file attributes on failure */
+       if (attrs != INVALID_FILE_ATTRIBUTES)
+               SetFileAttributesW(wpnew, attrs);
+
+       if (!is_file_in_use_error(gle)) {
+               errno = err_win_to_posix(gle);
+               return -1;
+       }
+
+       if (attrs == INVALID_FILE_ATTRIBUTES &&
            (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
                if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
                        DWORD attrsold = GetFileAttributesW(wpold);
@@ -2388,16 +2388,10 @@ repeat:
                        return -1;
                }
                if ((attrs & FILE_ATTRIBUTE_READONLY) &&
-                   SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
-                       if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
-                               return 0;
-                       gle = GetLastError();
-                       /* revert file attributes on failure */
-                       SetFileAttributesW(wpnew, attrs);
-               }
+                   SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
+                       goto repeat;
        }
-       if (gle == ERROR_ACCESS_DENIED &&
-              retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
+       if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
                       "Should I try again?", pold, pnew))
                goto repeat;