mingw: support renaming symlinks

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 <karsten.blees@gmail.com> Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Karsten Blees committed Jan 9, 2026 at 20:05 UTC 5e88e98c04267b44e4f822f297c60e1e8c852411
1 file changed +16 -22
compat/mingw.c
+16 -22
@@ -2275,7 +2275,7 @@ int mingw_accept(int sockfd1, struct sockaddr *sa, socklen_t *sz)
2275 int mingw_rename(const char *pold, const char *pnew)
2276 {
2277 static int supports_file_rename_info_ex = 1;
2278 - DWORD attrs, gle;
2278 + DWORD attrs = INVALID_FILE_ATTRIBUTES, gle;
2279 int tries = 0;
2280 wchar_t wpold[MAX_PATH], wpnew[MAX_PATH];
2281 int wpnew_len;
@@ -2286,15 +2286,6 @@ int mingw_rename(const char *pold, const char *pnew)
2286 if (wpnew_len < 0)
2287 return -1;
2288
2289 - /*
2290 - * Try native rename() first to get errno right.
2291 - * It is based on MoveFile(), which cannot overwrite existing files.
2292 - */
2293 - if (!_wrename(wpold, wpnew))
2294 - return 0;
2295 - if (errno != EEXIST)
2296 - return -1;
2297 -
2289 repeat:
2290 if (supports_file_rename_info_ex) {
2291 /*
@@ -2370,13 +2361,22 @@ repeat:
2361 * to retry.
2362 */
2363 } else {
2373 - if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
2364 + if (MoveFileExW(wpold, wpnew,
2365 + MOVEFILE_REPLACE_EXISTING | MOVEFILE_COPY_ALLOWED))
2366 return 0;
2367 gle = GetLastError();
2368 }
2369
2378 - /* TODO: translate more errors */
2379 - if (gle == ERROR_ACCESS_DENIED &&
2370 + /* revert file attributes on failure */
2371 + if (attrs != INVALID_FILE_ATTRIBUTES)
2372 + SetFileAttributesW(wpnew, attrs);
2373 +
2374 + if (!is_file_in_use_error(gle)) {
2375 + errno = err_win_to_posix(gle);
2376 + return -1;
2377 + }
2378 +
2379 + if (attrs == INVALID_FILE_ATTRIBUTES &&
2380 (attrs = GetFileAttributesW(wpnew)) != INVALID_FILE_ATTRIBUTES) {
2381 if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
2382 DWORD attrsold = GetFileAttributesW(wpold);
@@ -2388,16 +2388,10 @@ repeat:
2388 return -1;
2389 }
2390 if ((attrs & FILE_ATTRIBUTE_READONLY) &&
2391 - SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY)) {
2392 - if (MoveFileExW(wpold, wpnew, MOVEFILE_REPLACE_EXISTING))
2393 - return 0;
2394 - gle = GetLastError();
2395 - /* revert file attributes on failure */
2396 - SetFileAttributesW(wpnew, attrs);
2397 - }
2391 + SetFileAttributesW(wpnew, attrs & ~FILE_ATTRIBUTE_READONLY))
2392 + goto repeat;
2393 }
2399 - if (gle == ERROR_ACCESS_DENIED &&
2400 - retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
2394 + if (retry_ask_yes_no(&tries, "Rename from '%s' to '%s' failed. "
2395 "Should I try again?", pold, pnew))
2396 goto repeat;
2397