{"thread":{"id":"65562","subject":"[PATCH 0/2] maintenance(geometric): avoid deadlocks on Windows 10","startedAt":"2026-04-28T12:52:52Z","lastAt":"2026-05-08T13:20:31Z","messageCount":11,"participants":["Johannes Schindelin via GitGitGadget","Derrick Stolee","Patrick Steinhardt","Johannes Schindelin"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"542427","messageId":"pull.2103.git.1777380768.gitgitgadget@gmail.com","threadId":"65562","inReplyTo":null,"subject":"[PATCH 0/2] maintenance(geometric): avoid deadlocks on Windows 10","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T12:52:46Z","receivedAt":"2026-04-28T12:52:52Z","isPatch":true,"body":"On Windows, maintenance_task_geometric_repack() opens pack index files via\npack_geometry_init() (which mmap()s the .idx files), then spawns git repack\nas a child process without setting child.odb_to_close. The parent's mmap()s\nprevent the child from deleting old .idx files.\n\nOn Windows 10 builds before the POSIX delete semantics change (between Build\n17134.1304 and 18363.657, see https://stackoverflow.com/a/60512798), this\nresults in Unlink of file '.git/objects/pack/pack-<hash>.idx' failed. Should\nI try again? during fetch-triggered auto-maintenance with the geometric\nstrategy.\n\nThe fix adds the missing child.odb_to_close = the_repository->objects line,\nmatching all other maintenance tasks.\n\nThe first commit introduces a GIT_TEST_LEGACY_DELETE environment variable to\nsimulate legacy (pre-POSIX) delete semantics on modern Windows, so the\nregression test can verify the fix even on Windows 11.\n\nThis fixes https://github.com/git-for-windows/git/issues/6210.\n\nJohannes Schindelin (2):\n  mingw: optionally use legacy (non-POSIX) delete semantics\n  maintenance(geometric): do release the `.idx` files before repacking\n\n builtin/gc.c           |  1 +\n compat/mingw.c         | 47 ++++++++++++++++++++++++++++++++++++++++--\n t/t7900-maintenance.sh | 22 +++++++++++++++++++-\n 3 files changed, 67 insertions(+), 3 deletions(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2103%2Fdscho%2Favoid-deadlocks-in-geometric-repacking-on-windows-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2103/dscho/avoid-deadlocks-in-geometric-repacking-on-windows-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/2103\n-- \ngitgitgadget\n"},{"id":"542429","messageId":"97508e91b62c91b77447dbba39a84770682591a8.1777380768.git.gitgitgadget@gmail.com","threadId":"65562","inReplyTo":"pull.2103.git.1777380768.gitgitgadget@gmail.com","subject":"[PATCH 1/2] mingw: optionally use legacy (non-POSIX) delete semantics","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T12:52:47Z","receivedAt":"2026-04-28T12:52:56Z","isPatch":true,"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAt some point between Windows 10 Build 17134.1304 and Build 18363.657,\nthe default behavior of `DeleteFileW()` was changed to use POSIX\nsemantics (https://stackoverflow.com/a/60512798). Under those semantics,\na file can be deleted even when another process holds an active\n`MapViewOfFile` view on it: the directory entry is removed immediately,\nbut the underlying data persists until the last handle is closed.\n\nOn older Windows versions (and Windows 10 builds before that change),\n`DeleteFileW()` uses legacy semantics where deletion fails outright if\nany process holds a file mapping.\n\nTo allow testing code paths that depend on the legacy behavior, introduce\na `GIT_TEST_LEGACY_DELETE` environment variable. When set, `mingw_unlink()`\nuses `SetFileInformationByHandle()` with `FileDispositionInfo` (the\nnon-POSIX variant) instead of `DeleteFileW()`, forcing legacy delete\nsemantics regardless of the Windows version.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n compat/mingw.c | 47 +++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 45 insertions(+), 2 deletions(-)\n\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 2023c16db6..04f9aa3922 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -449,20 +449,63 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)\n \treturn wbuf;\n }\n \n+/*\n+ * Use SetFileInformationByHandle(FileDispositionInfo) to force legacy\n+ * (non-POSIX) delete semantics. On Windows 11, DeleteFileW() uses POSIX\n+ * delete semantics internally, allowing deletion even with active\n+ * MapViewOfFile views. This helper simulates Windows 10 behavior where\n+ * deletion fails if a file mapping exists.\n+ *\n+ * Returns nonzero on success (like DeleteFileW), 0 on failure.\n+ */\n+static int legacy_delete_file(const wchar_t *wpathname)\n+{\n+\tFILE_DISPOSITION_INFO fdi = { TRUE };\n+\tDWORD gle;\n+\tHANDLE h = CreateFileW(wpathname, DELETE,\n+\t\t\t       FILE_SHARE_READ | FILE_SHARE_WRITE |\n+\t\t\t       FILE_SHARE_DELETE,\n+\t\t\t       NULL, OPEN_EXISTING,\n+\t\t\t       FILE_FLAG_OPEN_REPARSE_POINT, NULL);\n+\tif (h == INVALID_HANDLE_VALUE)\n+\t\treturn 0;\n+\n+\tif (SetFileInformationByHandle(h, FileDispositionInfo,\n+\t\t\t\t       &fdi, sizeof(fdi))) {\n+\t\tCloseHandle(h);\n+\t\treturn 1;\n+\t}\n+\tgle = GetLastError();\n+\tCloseHandle(h);\n+\tSetLastError(gle);\n+\treturn 0;\n+}\n+\n+static int try_delete_file(const wchar_t *wpathname, int use_legacy)\n+{\n+\tif (use_legacy)\n+\t\treturn legacy_delete_file(wpathname);\n+\treturn DeleteFileW(wpathname);\n+}\n+\n int mingw_unlink(const char *pathname, int handle_in_use_error)\n {\n+\tstatic int use_legacy_delete = -1;\n \tint tries = 0;\n \twchar_t wpathname[MAX_PATH];\n \tif (xutftowcs_path(wpathname, pathname) < 0)\n \t\treturn -1;\n \n-\tif (DeleteFileW(wpathname))\n+\tif (use_legacy_delete < 0)\n+\t\tuse_legacy_delete = !!getenv(\"GIT_TEST_LEGACY_DELETE\");\n+\n+\tif (try_delete_file(wpathname, use_legacy_delete))\n \t\treturn 0;\n \n \tdo {\n \t\t/* read-only files cannot be removed */\n \t\t_wchmod(wpathname, 0666);\n-\t\tif (!_wunlink(wpathname))\n+\t\tif (try_delete_file(wpathname, use_legacy_delete))\n \t\t\treturn 0;\n \t\tif (!is_file_in_use_error(GetLastError()))\n \t\t\tbreak;\n-- \ngitgitgadget\n\n"},{"id":"542428","messageId":"12ebd5c56ff02cdbae87b0af9404bb666133bb04.1777380768.git.gitgitgadget@gmail.com","threadId":"65562","inReplyTo":"pull.2103.git.1777380768.gitgitgadget@gmail.com","subject":"[PATCH 2/2] maintenance(geometric): do release the `.idx` files before repacking","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-04-28T12:52:48Z","receivedAt":"2026-04-28T12:52:58Z","isPatch":true,"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAs is done for all the other maintenance tasks, let's release the ODB\nalso before starting the geometric repacking. That way, the `.idx` files\nwon't be `mmap()`ed when they are to be deleted (which does not work on\nWindows because you cannot delete files on that platform as long as they\nare kept open by a process).\n\nThis regression was introduced by 9bc151850c1c (builtin/maintenance:\nintroduce \"geometric-repack\" task, 2025-10-24), but was only noticed\nonce geometric repacking was made the default in 452b12c2e0fe (builtin/\nmaintenance: use \"geometric\" strategy by default, 2026-02-24).\n\nThe fix recapitulates my work from df76ee7b77f0 (run-command: offer to\nclose the object store before running, 2021-09-09) & friends.\n\nTo guard against future regressions of this kind, add a check to\n`run_and_verify_geometric_pack()` in `t7900` that detects orphaned\n`.idx` files left behind after repacking. Contrary to interactive\ncalls, the `git maintenance` call in that test case would _not_ block on\nWindows, asking whether to retry deleting that file, which is the reason\nwhy this bug was not caught earlier.\n\nFurthermore, since the default behavior of `DeleteFileW()` was changed\nat some point between Windows 10 Build 17134.1304 and Build 18363.657\nto use POSIX semantics (see https://stackoverflow.com/a/60512798),\nthe added orphaned-`.idx` check would be insufficient to catch this\nregression on modern Windows without emulating legacy delete semantics\nvia `GIT_TEST_LEGACY_DELETE=1`.\n\nThis fixes https://github.com/git-for-windows/git/issues/6210.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/gc.c           |  1 +\n t/t7900-maintenance.sh | 22 +++++++++++++++++++++-\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 3a71e314c9..84a66d3240 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1590,6 +1590,7 @@ static int maintenance_task_geometric_repack(struct maintenance_run_opts *opts,\n \tpack_geometry_split(&geometry);\n \n \tchild.git_cmd = 1;\n+\tchild.odb_to_close = the_repository->objects;\n \n \tstrvec_pushl(&child.args, \"repack\", \"-d\", \"-l\", NULL);\n \tif (geometry.split < geometry.pack_nr)\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 4700beacc1..f497f51b23 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -532,7 +532,16 @@ run_and_verify_geometric_pack () {\n \n \t# And verify that there are no loose objects anymore.\n \tgit count-objects -v >count &&\n-\ttest_grep '^count: 0$' count\n+\ttest_grep '^count: 0$' count &&\n+\n+\t# Verify that no orphaned .idx files were left behind. On\n+\t# Windows, a missing odb_to_close causes the parent to hold\n+\t# mmap handles on .idx files, silently preventing their\n+\t# deletion by the child git-repack process.\n+\tls .git/objects/pack/pack-*.idx .git/objects/pack/pack-*.pack |\n+\tsed \"s/\\.pack$/.idx/\" |\n+\tsort | uniq -u >orphaned-idx &&\n+\ttest_must_be_empty orphaned-idx\n }\n \n test_expect_success 'geometric repacking task' '\n@@ -580,8 +589,19 @@ test_expect_success 'geometric repacking task' '\n \n \t\t# And these two small packs should now be merged via the\n \t\t# geometric repack. The large packfile should remain intact.\n+\t\tcp -R .git/objects .git/objects.save &&\n \t\trun_and_verify_geometric_pack 2 &&\n \n+\t\t# On Windows, verify the same with legacy delete semantics\n+\t\t# that reject deletion of mmap-held .idx files.\n+\t\tif test_have_prereq MINGW\n+\t\tthen\n+\t\t\trm -rf .git/objects &&\n+\t\t\tmv .git/objects.save .git/objects &&\n+\t\t\ttest_env GIT_TEST_LEGACY_DELETE=1 \\\n+\t\t\t\trun_and_verify_geometric_pack 2\n+\t\tfi &&\n+\n \t\t# If we now add two more objects and repack twice we should\n \t\t# then see another all-into-one repack. This time around\n \t\t# though, as we have unreachable objects, we should also see a\n-- \ngitgitgadget\n"},{"id":"542432","messageId":"5c358919-0dcf-41c8-bdf7-912c41f77c31@gmail.com","threadId":"65562","inReplyTo":"pull.2103.git.1777380768.gitgitgadget@gmail.com","subject":"Re: [PATCH 0/2] maintenance(geometric): avoid deadlocks on Windows 10","fromName":"Derrick Stolee","fromEmail":"stolee@gmail.com","sentAt":"2026-04-28T15:01:34Z","receivedAt":"2026-04-28T15:01:38Z","isPatch":true,"body":"On 4/28/2026 8:52 AM, Johannes Schindelin via GitGitGadget wrote:\n> On Windows, maintenance_task_geometric_repack() opens pack index files via\n> pack_geometry_init() (which mmap()s the .idx files), then spawns git repack\n> as a child process without setting child.odb_to_close. The parent's mmap()s\n> prevent the child from deleting old .idx files.\n> \n> On Windows 10 builds before the POSIX delete semantics change (between Build\n> 17134.1304 and 18363.657, see https://stackoverflow.com/a/60512798), this\n> results in Unlink of file '.git/objects/pack/pack-<hash>.idx' failed. Should\n> I try again? during fetch-triggered auto-maintenance with the geometric\n> strategy.\n> \n> The fix adds the missing child.odb_to_close = the_repository->objects line,\n> matching all other maintenance tasks.\n> \n> The first commit introduces a GIT_TEST_LEGACY_DELETE environment variable to\n> simulate legacy (pre-POSIX) delete semantics on modern Windows, so the\n> regression test can verify the fix even on Windows 11.\n> \n> This fixes https://github.com/git-for-windows/git/issues/6210.\nThanks for these patches. I reviewed their equivalents in the\ngit-for-windows/git fork so I'll give my LGTM here, too.\n\nThanks,\n-Stolee\n"},{"id":"542675","messageId":"afipTWyj2zVYYqMz@pks.im","threadId":"65562","inReplyTo":"97508e91b62c91b77447dbba39a84770682591a8.1777380768.git.gitgitgadget@gmail.com","subject":"Re: [PATCH 1/2] mingw: optionally use legacy (non-POSIX) delete semantics","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-04T14:12:29Z","receivedAt":"2026-05-04T14:12:36Z","isPatch":true,"body":"On Tue, Apr 28, 2026 at 12:52:47PM +0000, Johannes Schindelin via GitGitGadget wrote:\n> diff --git a/compat/mingw.c b/compat/mingw.c\n> index 2023c16db6..04f9aa3922 100644\n> --- a/compat/mingw.c\n> +++ b/compat/mingw.c\n> @@ -449,20 +449,63 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)\n>  \treturn wbuf;\n>  }\n>  \n> +/*\n> + * Use SetFileInformationByHandle(FileDispositionInfo) to force legacy\n> + * (non-POSIX) delete semantics. On Windows 11, DeleteFileW() uses POSIX\n> + * delete semantics internally, allowing deletion even with active\n> + * MapViewOfFile views. This helper simulates Windows 10 behavior where\n> + * deletion fails if a file mapping exists.\n> + *\n> + * Returns nonzero on success (like DeleteFileW), 0 on failure.\n> + */\n> +static int legacy_delete_file(const wchar_t *wpathname)\n> +{\n> +\tFILE_DISPOSITION_INFO fdi = { TRUE };\n> +\tDWORD gle;\n> +\tHANDLE h = CreateFileW(wpathname, DELETE,\n> +\t\t\t       FILE_SHARE_READ | FILE_SHARE_WRITE |\n> +\t\t\t       FILE_SHARE_DELETE,\n> +\t\t\t       NULL, OPEN_EXISTING,\n> +\t\t\t       FILE_FLAG_OPEN_REPARSE_POINT, NULL);\n> +\tif (h == INVALID_HANDLE_VALUE)\n> +\t\treturn 0;\n> +\n> +\tif (SetFileInformationByHandle(h, FileDispositionInfo,\n> +\t\t\t\t       &fdi, sizeof(fdi))) {\n> +\t\tCloseHandle(h);\n> +\t\treturn 1;\n> +\t}\n> +\tgle = GetLastError();\n> +\tCloseHandle(h);\n> +\tSetLastError(gle);\n> +\treturn 0;\n> +}\n> +\n> +static int try_delete_file(const wchar_t *wpathname, int use_legacy)\n> +{\n> +\tif (use_legacy)\n> +\t\treturn legacy_delete_file(wpathname);\n> +\treturn DeleteFileW(wpathname);\n> +}\n> +\n>  int mingw_unlink(const char *pathname, int handle_in_use_error)\n>  {\n> +\tstatic int use_legacy_delete = -1;\n>  \tint tries = 0;\n>  \twchar_t wpathname[MAX_PATH];\n>  \tif (xutftowcs_path(wpathname, pathname) < 0)\n>  \t\treturn -1;\n>  \n> -\tif (DeleteFileW(wpathname))\n> +\tif (use_legacy_delete < 0)\n> +\t\tuse_legacy_delete = !!getenv(\"GIT_TEST_LEGACY_DELETE\");\n\nShould this use `git_env_bool()`?\n\nPatrick\n"},{"id":"542676","messageId":"afipUq2HqJrcPvLo@pks.im","threadId":"65562","inReplyTo":"5c358919-0dcf-41c8-bdf7-912c41f77c31@gmail.com","subject":"Re: [PATCH 0/2] maintenance(geometric): avoid deadlocks on Windows 10","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-04T14:12:34Z","receivedAt":"2026-05-04T14:12:39Z","isPatch":true,"body":"On Tue, Apr 28, 2026 at 11:01:34AM -0400, Derrick Stolee wrote:\n> On 4/28/2026 8:52 AM, Johannes Schindelin via GitGitGadget wrote:\n> > On Windows, maintenance_task_geometric_repack() opens pack index files via\n> > pack_geometry_init() (which mmap()s the .idx files), then spawns git repack\n> > as a child process without setting child.odb_to_close. The parent's mmap()s\n> > prevent the child from deleting old .idx files.\n> > \n> > On Windows 10 builds before the POSIX delete semantics change (between Build\n> > 17134.1304 and 18363.657, see https://stackoverflow.com/a/60512798), this\n> > results in Unlink of file '.git/objects/pack/pack-<hash>.idx' failed. Should\n> > I try again? during fetch-triggered auto-maintenance with the geometric\n> > strategy.\n> > \n> > The fix adds the missing child.odb_to_close = the_repository->objects line,\n> > matching all other maintenance tasks.\n> > \n> > The first commit introduces a GIT_TEST_LEGACY_DELETE environment variable to\n> > simulate legacy (pre-POSIX) delete semantics on modern Windows, so the\n> > regression test can verify the fix even on Windows 11.\n> > \n> > This fixes https://github.com/git-for-windows/git/issues/6210.\n> \n> Thanks for these patches. I reviewed their equivalents in the\n> git-for-windows/git fork so I'll give my LGTM here, too.\n\nI've got a single comment on the first patch, but other than that this\nseries looks good to me. Thanks!\n\nPatrick\n"},{"id":"542841","messageId":"c980cb5c-cab1-7ff9-d057-8df5e661a5ac@gmx.de","threadId":"65562","inReplyTo":"afipTWyj2zVYYqMz@pks.im","subject":"Re: [PATCH 1/2] mingw: optionally use legacy (non-POSIX) delete semantics","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2026-05-07T12:49:34Z","receivedAt":"2026-05-07T12:49:40Z","isPatch":true,"body":"Hi Patrick,\n\nOn Thu, 7 May 2026, Patrick Steinhardt wrote:\n\n> On Tue, Apr 28, 2026 at 12:52:47PM +0000, Johannes Schindelin via GitGitGadget wrote:\n> >  \n> > -\tif (DeleteFileW(wpathname))\n> > +\tif (use_legacy_delete < 0)\n> > +\t\tuse_legacy_delete = !!getenv(\"GIT_TEST_LEGACY_DELETE\");\n> \n> Should this use `git_env_bool()`?\n\nYes! Will fix in v2.\n\nCiao,\nJohannes\n"},{"id":"542842","messageId":"pull.2103.v2.git.1778158273.gitgitgadget@gmail.com","threadId":"65562","inReplyTo":"pull.2103.git.1777380768.gitgitgadget@gmail.com","subject":"[PATCH v2 0/2] maintenance(geometric): avoid deadlocks on Windows 10","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-07T12:51:11Z","receivedAt":"2026-05-07T12:51:16Z","isPatch":true,"body":"On Windows, maintenance_task_geometric_repack() opens pack index files via\npack_geometry_init() (which mmap()s the .idx files), then spawns git repack\nas a child process without setting child.odb_to_close. The parent's mmap()s\nprevent the child from deleting old .idx files.\n\nOn Windows 10 builds before the POSIX delete semantics change (between Build\n17134.1304 and 18363.657, see https://stackoverflow.com/a/60512798), this\nresults in Unlink of file '.git/objects/pack/pack-<hash>.idx' failed. Should\nI try again? during fetch-triggered auto-maintenance with the geometric\nstrategy.\n\nThe fix adds the missing child.odb_to_close = the_repository->objects line,\nmatching all other maintenance tasks.\n\nThe first commit introduces a GIT_TEST_LEGACY_DELETE environment variable to\nsimulate legacy (pre-POSIX) delete semantics on modern Windows, so the\nregression test can verify the fix even on Windows 11.\n\nThis fixes https://github.com/git-for-windows/git/issues/6210.\n\nChanges since v1:\n\n * The code now uses git_env_bool() as appropriate (thanks Patrick!)\n\nJohannes Schindelin (2):\n  mingw: optionally use legacy (non-POSIX) delete semantics\n  maintenance(geometric): do release the `.idx` files before repacking\n\n builtin/gc.c           |  1 +\n compat/mingw.c         | 47 ++++++++++++++++++++++++++++++++++++++++--\n t/t7900-maintenance.sh | 22 +++++++++++++++++++-\n 3 files changed, 67 insertions(+), 3 deletions(-)\n\n\nbase-commit: 94f057755b7941b321fd11fec1b2e3ca5313a4e0\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-2103%2Fdscho%2Favoid-deadlocks-in-geometric-repacking-on-windows-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-2103/dscho/avoid-deadlocks-in-geometric-repacking-on-windows-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/2103\n\nRange-diff vs v1:\n\n 1:  97508e91b6 ! 1:  8ee749dd2f mingw: optionally use legacy (non-POSIX) delete semantics\n     @@ compat/mingw.c: static wchar_t *normalize_ntpath(wchar_t *wbuf)\n       \n      -\tif (DeleteFileW(wpathname))\n      +\tif (use_legacy_delete < 0)\n     -+\t\tuse_legacy_delete = !!getenv(\"GIT_TEST_LEGACY_DELETE\");\n     ++\t\tuse_legacy_delete = git_env_bool(\"GIT_TEST_LEGACY_DELETE\", 0);\n      +\n      +\tif (try_delete_file(wpathname, use_legacy_delete))\n       \t\treturn 0;\n 2:  12ebd5c56f = 2:  66219b79fa maintenance(geometric): do release the `.idx` files before repacking\n\n-- \ngitgitgadget\n"},{"id":"542843","messageId":"8ee749dd2fea06811e929d247e34132cb5c95ead.1778158273.git.gitgitgadget@gmail.com","threadId":"65562","inReplyTo":"pull.2103.v2.git.1778158273.gitgitgadget@gmail.com","subject":"[PATCH v2 1/2] mingw: optionally use legacy (non-POSIX) delete semantics","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-07T12:51:12Z","receivedAt":"2026-05-07T12:51:17Z","isPatch":true,"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAt some point between Windows 10 Build 17134.1304 and Build 18363.657,\nthe default behavior of `DeleteFileW()` was changed to use POSIX\nsemantics (https://stackoverflow.com/a/60512798). Under those semantics,\na file can be deleted even when another process holds an active\n`MapViewOfFile` view on it: the directory entry is removed immediately,\nbut the underlying data persists until the last handle is closed.\n\nOn older Windows versions (and Windows 10 builds before that change),\n`DeleteFileW()` uses legacy semantics where deletion fails outright if\nany process holds a file mapping.\n\nTo allow testing code paths that depend on the legacy behavior, introduce\na `GIT_TEST_LEGACY_DELETE` environment variable. When set, `mingw_unlink()`\nuses `SetFileInformationByHandle()` with `FileDispositionInfo` (the\nnon-POSIX variant) instead of `DeleteFileW()`, forcing legacy delete\nsemantics regardless of the Windows version.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n compat/mingw.c | 47 +++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 45 insertions(+), 2 deletions(-)\n\ndiff --git a/compat/mingw.c b/compat/mingw.c\nindex 2023c16db6..aa7525f419 100644\n--- a/compat/mingw.c\n+++ b/compat/mingw.c\n@@ -449,20 +449,63 @@ static wchar_t *normalize_ntpath(wchar_t *wbuf)\n \treturn wbuf;\n }\n \n+/*\n+ * Use SetFileInformationByHandle(FileDispositionInfo) to force legacy\n+ * (non-POSIX) delete semantics. On Windows 11, DeleteFileW() uses POSIX\n+ * delete semantics internally, allowing deletion even with active\n+ * MapViewOfFile views. This helper simulates Windows 10 behavior where\n+ * deletion fails if a file mapping exists.\n+ *\n+ * Returns nonzero on success (like DeleteFileW), 0 on failure.\n+ */\n+static int legacy_delete_file(const wchar_t *wpathname)\n+{\n+\tFILE_DISPOSITION_INFO fdi = { TRUE };\n+\tDWORD gle;\n+\tHANDLE h = CreateFileW(wpathname, DELETE,\n+\t\t\t       FILE_SHARE_READ | FILE_SHARE_WRITE |\n+\t\t\t       FILE_SHARE_DELETE,\n+\t\t\t       NULL, OPEN_EXISTING,\n+\t\t\t       FILE_FLAG_OPEN_REPARSE_POINT, NULL);\n+\tif (h == INVALID_HANDLE_VALUE)\n+\t\treturn 0;\n+\n+\tif (SetFileInformationByHandle(h, FileDispositionInfo,\n+\t\t\t\t       &fdi, sizeof(fdi))) {\n+\t\tCloseHandle(h);\n+\t\treturn 1;\n+\t}\n+\tgle = GetLastError();\n+\tCloseHandle(h);\n+\tSetLastError(gle);\n+\treturn 0;\n+}\n+\n+static int try_delete_file(const wchar_t *wpathname, int use_legacy)\n+{\n+\tif (use_legacy)\n+\t\treturn legacy_delete_file(wpathname);\n+\treturn DeleteFileW(wpathname);\n+}\n+\n int mingw_unlink(const char *pathname, int handle_in_use_error)\n {\n+\tstatic int use_legacy_delete = -1;\n \tint tries = 0;\n \twchar_t wpathname[MAX_PATH];\n \tif (xutftowcs_path(wpathname, pathname) < 0)\n \t\treturn -1;\n \n-\tif (DeleteFileW(wpathname))\n+\tif (use_legacy_delete < 0)\n+\t\tuse_legacy_delete = git_env_bool(\"GIT_TEST_LEGACY_DELETE\", 0);\n+\n+\tif (try_delete_file(wpathname, use_legacy_delete))\n \t\treturn 0;\n \n \tdo {\n \t\t/* read-only files cannot be removed */\n \t\t_wchmod(wpathname, 0666);\n-\t\tif (!_wunlink(wpathname))\n+\t\tif (try_delete_file(wpathname, use_legacy_delete))\n \t\t\treturn 0;\n \t\tif (!is_file_in_use_error(GetLastError()))\n \t\t\tbreak;\n-- \ngitgitgadget\n\n"},{"id":"542844","messageId":"66219b79fa3808a85d2c0e1aee03a4979ca01116.1778158273.git.gitgitgadget@gmail.com","threadId":"65562","inReplyTo":"pull.2103.v2.git.1778158273.gitgitgadget@gmail.com","subject":"[PATCH v2 2/2] maintenance(geometric): do release the `.idx` files before repacking","fromName":"Johannes Schindelin via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-05-07T12:51:13Z","receivedAt":"2026-05-07T12:51:18Z","isPatch":true,"body":"From: Johannes Schindelin <johannes.schindelin@gmx.de>\n\nAs is done for all the other maintenance tasks, let's release the ODB\nalso before starting the geometric repacking. That way, the `.idx` files\nwon't be `mmap()`ed when they are to be deleted (which does not work on\nWindows because you cannot delete files on that platform as long as they\nare kept open by a process).\n\nThis regression was introduced by 9bc151850c1c (builtin/maintenance:\nintroduce \"geometric-repack\" task, 2025-10-24), but was only noticed\nonce geometric repacking was made the default in 452b12c2e0fe (builtin/\nmaintenance: use \"geometric\" strategy by default, 2026-02-24).\n\nThe fix recapitulates my work from df76ee7b77f0 (run-command: offer to\nclose the object store before running, 2021-09-09) & friends.\n\nTo guard against future regressions of this kind, add a check to\n`run_and_verify_geometric_pack()` in `t7900` that detects orphaned\n`.idx` files left behind after repacking. Contrary to interactive\ncalls, the `git maintenance` call in that test case would _not_ block on\nWindows, asking whether to retry deleting that file, which is the reason\nwhy this bug was not caught earlier.\n\nFurthermore, since the default behavior of `DeleteFileW()` was changed\nat some point between Windows 10 Build 17134.1304 and Build 18363.657\nto use POSIX semantics (see https://stackoverflow.com/a/60512798),\nthe added orphaned-`.idx` check would be insufficient to catch this\nregression on modern Windows without emulating legacy delete semantics\nvia `GIT_TEST_LEGACY_DELETE=1`.\n\nThis fixes https://github.com/git-for-windows/git/issues/6210.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n builtin/gc.c           |  1 +\n t/t7900-maintenance.sh | 22 +++++++++++++++++++++-\n 2 files changed, 22 insertions(+), 1 deletion(-)\n\ndiff --git a/builtin/gc.c b/builtin/gc.c\nindex 3a71e314c9..84a66d3240 100644\n--- a/builtin/gc.c\n+++ b/builtin/gc.c\n@@ -1590,6 +1590,7 @@ static int maintenance_task_geometric_repack(struct maintenance_run_opts *opts,\n \tpack_geometry_split(&geometry);\n \n \tchild.git_cmd = 1;\n+\tchild.odb_to_close = the_repository->objects;\n \n \tstrvec_pushl(&child.args, \"repack\", \"-d\", \"-l\", NULL);\n \tif (geometry.split < geometry.pack_nr)\ndiff --git a/t/t7900-maintenance.sh b/t/t7900-maintenance.sh\nindex 4700beacc1..f497f51b23 100755\n--- a/t/t7900-maintenance.sh\n+++ b/t/t7900-maintenance.sh\n@@ -532,7 +532,16 @@ run_and_verify_geometric_pack () {\n \n \t# And verify that there are no loose objects anymore.\n \tgit count-objects -v >count &&\n-\ttest_grep '^count: 0$' count\n+\ttest_grep '^count: 0$' count &&\n+\n+\t# Verify that no orphaned .idx files were left behind. On\n+\t# Windows, a missing odb_to_close causes the parent to hold\n+\t# mmap handles on .idx files, silently preventing their\n+\t# deletion by the child git-repack process.\n+\tls .git/objects/pack/pack-*.idx .git/objects/pack/pack-*.pack |\n+\tsed \"s/\\.pack$/.idx/\" |\n+\tsort | uniq -u >orphaned-idx &&\n+\ttest_must_be_empty orphaned-idx\n }\n \n test_expect_success 'geometric repacking task' '\n@@ -580,8 +589,19 @@ test_expect_success 'geometric repacking task' '\n \n \t\t# And these two small packs should now be merged via the\n \t\t# geometric repack. The large packfile should remain intact.\n+\t\tcp -R .git/objects .git/objects.save &&\n \t\trun_and_verify_geometric_pack 2 &&\n \n+\t\t# On Windows, verify the same with legacy delete semantics\n+\t\t# that reject deletion of mmap-held .idx files.\n+\t\tif test_have_prereq MINGW\n+\t\tthen\n+\t\t\trm -rf .git/objects &&\n+\t\t\tmv .git/objects.save .git/objects &&\n+\t\t\ttest_env GIT_TEST_LEGACY_DELETE=1 \\\n+\t\t\t\trun_and_verify_geometric_pack 2\n+\t\tfi &&\n+\n \t\t# If we now add two more objects and repack twice we should\n \t\t# then see another all-into-one repack. This time around\n \t\t# though, as we have unreachable objects, we should also see a\n-- \ngitgitgadget\n"},{"id":"542918","messageId":"af3jGSif1UH5tY-K@pks.im","threadId":"65562","inReplyTo":"pull.2103.v2.git.1778158273.gitgitgadget@gmail.com","subject":"Re: [PATCH v2 0/2] maintenance(geometric): avoid deadlocks on Windows 10","fromName":"Patrick Steinhardt","fromEmail":"ps@pks.im","sentAt":"2026-05-08T13:20:25Z","receivedAt":"2026-05-08T13:20:31Z","isPatch":true,"body":"On Thu, May 07, 2026 at 12:51:11PM +0000, Johannes Schindelin via GitGitGadget wrote:\n> On Windows, maintenance_task_geometric_repack() opens pack index files via\n> pack_geometry_init() (which mmap()s the .idx files), then spawns git repack\n> as a child process without setting child.odb_to_close. The parent's mmap()s\n> prevent the child from deleting old .idx files.\n> \n> On Windows 10 builds before the POSIX delete semantics change (between Build\n> 17134.1304 and 18363.657, see https://stackoverflow.com/a/60512798), this\n> results in Unlink of file '.git/objects/pack/pack-<hash>.idx' failed. Should\n> I try again? during fetch-triggered auto-maintenance with the geometric\n> strategy.\n> \n> The fix adds the missing child.odb_to_close = the_repository->objects line,\n> matching all other maintenance tasks.\n> \n> The first commit introduces a GIT_TEST_LEGACY_DELETE environment variable to\n> simulate legacy (pre-POSIX) delete semantics on modern Windows, so the\n> regression test can verify the fix even on Windows 11.\n> \n> This fixes https://github.com/git-for-windows/git/issues/6210.\n> \n> Changes since v1:\n> \n>  * The code now uses git_env_bool() as appropriate (thanks Patrick!)\n\nThis version looks good to me, thanks!\n\nPatrick\n"}]}