From: "Jason Newton via GitGitGadget" <gitgitgadget@gmail.com>
To: git@vger.kernel.org
Cc: "brian m. carlson" <sandals@crustytoothpaste.net>,
Jason Newton <nevion@gmail.com>, Jason Newton <nevion@gmail.com>
Subject: [PATCH v3 3/4] worktree: disable reflink on Windows
Date: Wed, 21 Oct 2026 07:17:40 +0000 [thread overview]
Message-ID: <91806975a6909265b1325206ef2f33a169393920.1792567061.git.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2317.v3.git.git.1792567061.gitgitgadget@gmail.com>
From: Jason Newton <nevion@gmail.com>
FSCTL_DUPLICATE_EXTENTS_TO_FILE can publish zeros when the source
data is still cached. git-lfs issue 6312 reports that on Windows 11
build 26200, and a later reproduction hit build 26200.9448. No fixed
Windows build is identified, so reflink_file() refuses on every
Windows version.
git worktree add --reflink and worktree.reflink=true warn with that
build number and fall back to a regular checkout. worktree.reflink=auto
stays silent, as it does when the filesystem cannot reflink.
Signed-off-by: Jason Newton <nevion@gmail.com>
---
Documentation/config/worktree.adoc | 6 +-
Documentation/git-worktree.adoc | 12 ++-
builtin/worktree.c | 13 +++
copy.c | 133 +++--------------------------
t/t2400-worktree-add.sh | 15 ++++
5 files changed, 53 insertions(+), 126 deletions(-)
diff --git a/Documentation/config/worktree.adoc b/Documentation/config/worktree.adoc
index aa849c08d2..e221efc615 100644
--- a/Documentation/config/worktree.adoc
+++ b/Documentation/config/worktree.adoc
@@ -25,6 +25,8 @@ thus making it incompatible with older versions of Git.
(see linkgit:git-worktree[1]). May be set to "`true`", "`false`"
(the default), or "`auto`". With "`true`", a filesystem that does
not support reflinks produces a warning before falling back to an
- ordinary checkout; with "`auto`", the fallback is silent. An
- explicit `--reflink` or `--no-reflink` on the command line
+ ordinary checkout; with "`auto`", the fallback is silent. On Windows
+ the request always falls back. An explicit request warns that block
+ cloning can zero-fill files on Windows build 26200; "`auto`" stays
+ silent. An explicit `--reflink` or `--no-reflink` on the command line
overrides this setting.
diff --git a/Documentation/git-worktree.adoc b/Documentation/git-worktree.adoc
index 99e6e55be4..6cc7c1f27a 100644
--- a/Documentation/git-worktree.adoc
+++ b/Documentation/git-worktree.adoc
@@ -244,10 +244,14 @@ To remove a locked worktree, specify `--force` twice.
+
Reflinks require a copy-on-write filesystem: for example btrfs, XFS,
bcachefs or ZFS on Linux (and overlayfs on top of one of these, as used by
-containers), APFS on macOS, or ReFS on Windows. On a filesystem that does
-not support reflinks, `--reflink` transparently falls back to an ordinary
-checkout. Because the source worktree's untracked and ignored files are
-cloned as well, only use `--reflink` when that is acceptable.
+containers), or APFS on macOS. Reflink is disabled on Windows. ReFS block
+cloning can zero-fill files on Windows build 26200 (git-lfs issue 6312),
+and no fixed build is known. On Windows, `--reflink` falls back to an
+ordinary checkout and, unless `worktree.reflink` is `auto`, warns with
+that build number. On a filesystem that does not support reflinks,
+`--reflink` transparently falls back to an ordinary checkout. Because the
+source worktree's untracked and ignored files are cloned as well, only use
+`--reflink` when that is acceptable.
+
This option cannot be combined with `--no-checkout` or `--orphan`. It can
be enabled by default with the `worktree.reflink` configuration; see
diff --git a/builtin/worktree.c b/builtin/worktree.c
index 1059429cbb..93fcaa5ecd 100644
--- a/builtin/worktree.c
+++ b/builtin/worktree.c
@@ -563,6 +563,18 @@ static int reflink_worktree(const char *path, const char *wt_git_dir,
return error(_("--reflink needs a source working tree, but this "
"repository does not have one"));
+#ifdef GIT_WINDOWS_NATIVE
+ /*
+ * reflink_file() refuses on Windows. Build 26200 is the version
+ * named in git-lfs issue 6312; no later build is known to be fixed.
+ * Auto mode stays quiet, matching the non-Windows fallback.
+ */
+ if (!opts->quiet && opts->reflink != REFLINK_AUTO)
+ warning(_("reflink is disabled on Windows; block cloning "
+ "can zero-fill files on Windows build 26200; "
+ "falling back to a regular checkout"));
+ return 1;
+#else
if (!reflink_supported(path)) {
/* In auto mode the fallback is expected, so stay quiet. */
if (!opts->quiet && opts->reflink != REFLINK_AUTO)
@@ -570,6 +582,7 @@ static int reflink_worktree(const char *path, const char *wt_git_dir,
"falling back to a regular checkout"), path);
return 1;
}
+#endif
if (stat(path, &dst_st))
return error_errno(_("could not stat '%s'"), path);
diff --git a/copy.c b/copy.c
index e6dfe33f6a..2eb4c7477e 100644
--- a/copy.c
+++ b/copy.c
@@ -18,23 +18,6 @@
#elif defined(__APPLE__)
#include <sys/attr.h>
#include <sys/clonefile.h>
-#elif defined(GIT_WINDOWS_NATIVE)
-#include <winioctl.h>
-/*
- * Block cloning on ReFS uses FSCTL_DUPLICATE_EXTENTS_TO_FILE. Older Windows
- * SDK headers may lack the control code and its payload struct, so define
- * them ourselves when missing (the values are part of the stable Win32 API).
- */
-#ifndef FSCTL_DUPLICATE_EXTENTS_TO_FILE
-#define FSCTL_DUPLICATE_EXTENTS_TO_FILE \
- CTL_CODE(FILE_DEVICE_FILE_SYSTEM, 209, METHOD_BUFFERED, FILE_WRITE_DATA)
-typedef struct _DUPLICATE_EXTENTS_DATA {
- HANDLE FileHandle;
- LARGE_INTEGER SourceFileOffset;
- LARGE_INTEGER TargetFileOffset;
- LARGE_INTEGER ByteCount;
-} DUPLICATE_EXTENTS_DATA, *PDUPLICATE_EXTENTS_DATA;
-#endif
#endif
int copy_fd(int ifd, int ofd)
@@ -105,29 +88,6 @@ int copy_file_with_time(struct repository *repo,
return status;
}
-#ifdef GIT_WINDOWS_NATIVE
-/*
- * Clone [offset, offset+count) of "src" onto "dst" via ReFS block cloning.
- * "count" is rounded up to a cluster boundary by the caller; a range that
- * runs past the source's end of file is harmless and ignored by Windows.
- */
-static int clone_extent_windows(HANDLE dst, HANDLE src,
- uint64_t offset, uint64_t count)
-{
- DUPLICATE_EXTENTS_DATA dup;
- DWORD bytes;
-
- dup.FileHandle = src;
- dup.SourceFileOffset.QuadPart = offset;
- dup.TargetFileOffset.QuadPart = offset;
- dup.ByteCount.QuadPart = count;
- if (!DeviceIoControl(dst, FSCTL_DUPLICATE_EXTENTS_TO_FILE,
- &dup, sizeof(dup), NULL, 0, &bytes, NULL))
- return -1;
- return 0;
-}
-#endif
-
int reflink_file(struct repository *repo,
const char *dst, const char *src, int mode)
{
@@ -171,87 +131,20 @@ int reflink_file(struct repository *repo,
return 0;
#elif defined(GIT_WINDOWS_NATIVE)
/*
- * ReFS supports copy-on-write block cloning via
- * FSCTL_DUPLICATE_EXTENTS_TO_FILE, the same mechanism Git LFS uses.
- * Cloned ranges must start and end on a cluster boundary (ReFS uses
- * 64K or 4K clusters) and span less than 4GiB, so clone in <=1GiB
- * chunks and round the final chunk up to a cluster; the destination is
- * sized to the source's exact length and an over-long final range past
- * the source's EOF is ignored.
+ * ReFS block cloning (FSCTL_DUPLICATE_EXTENTS_TO_FILE) is disabled.
+ * On Windows 11 build 26200 the ioctl remaps on-disk clusters, so a
+ * clone of data still sitting in the cache captures unwritten zeros.
+ * The later flush of the source does not update the clone, and the
+ * zeros are permanent. That is git-lfs issue 6312, reproduced on
+ * build 26200.9448. No fixed Windows build is identified, so refuse
+ * on every Windows version and let the caller fall back to a copy.
*/
- const uint64_t GiB = (uint64_t)1 << 30;
- const uint64_t clusters[] = { 64 * 1024, 4 * 1024 };
- int fdi, fdo, ret = -1;
- HANDLE hi, ho;
- LARGE_INTEGER size;
- uint64_t off;
- size_t i;
-
- mode = (mode & 0111) ? 0777 : 0666;
- if ((fdi = open(src, O_RDONLY | O_BINARY)) < 0)
- return -1;
- if ((fdo = open(dst, O_RDWR | O_CREAT | O_EXCL | O_BINARY, mode)) < 0) {
- int saved = errno;
- close(fdi);
- errno = saved;
- return -1;
- }
- hi = (HANDLE)_get_osfhandle(fdi);
- ho = (HANDLE)_get_osfhandle(fdo);
- if (hi == INVALID_HANDLE_VALUE || ho == INVALID_HANDLE_VALUE ||
- !GetFileSizeEx(hi, &size)) {
- errno = EINVAL;
- goto cleanup;
- }
-
- /* the destination must be the source's exact size before cloning */
- if (!SetFilePointerEx(ho, size, NULL, FILE_BEGIN) || !SetEndOfFile(ho)) {
- errno = EIO;
- goto cleanup;
- }
-
- ret = 0;
- /* clone whole 1GiB regions (already cluster aligned) */
- for (off = 0; (uint64_t)size.QuadPart > off &&
- (uint64_t)size.QuadPart - off > GiB; off += GiB) {
- if (clone_extent_windows(ho, hi, off, GiB)) {
- errno = ENOSYS;
- ret = -1;
- goto cleanup;
- }
- }
- /* clone the tail, rounding its length up to a cluster (64K then 4K) */
- if (!ret && (uint64_t)size.QuadPart > off) {
- uint64_t rem = (uint64_t)size.QuadPart - off;
-
- ret = -1;
- for (i = 0; i < ARRAY_SIZE(clusters); i++) {
- uint64_t count = (rem + clusters[i] - 1) &
- ~(clusters[i] - 1);
- if (!clone_extent_windows(ho, hi, off, count)) {
- ret = 0;
- break;
- }
- }
- if (ret)
- errno = ENOSYS; /* not a CoW volume */
- }
-
-cleanup:
- close(fdi);
- if (close(fdo) && !ret) {
- errno = EIO;
- ret = -1;
- }
- if (ret) {
- int saved = errno;
- unlink(dst);
- errno = saved;
- return -1;
- }
- if (adjust_shared_perm(repo, dst))
- return -1;
- return 0;
+ (void)repo;
+ (void)dst;
+ (void)src;
+ (void)mode;
+ errno = ENOSYS;
+ return -1;
#else
/* No reflink primitive on this platform. */
(void)repo;
diff --git a/t/t2400-worktree-add.sh b/t/t2400-worktree-add.sh
index 14bf455736..592f414955 100755
--- a/t/t2400-worktree-add.sh
+++ b/t/t2400-worktree-add.sh
@@ -1288,6 +1288,21 @@ test_expect_success 'relative worktree sets extension config' '
test_cmp_config -C repo true extensions.relativeworktrees
'
+test_expect_success MINGW 'reflink is disabled on Windows build 26200' '
+ test_when_finished "rm -rf winreflink" &&
+ git init winreflink &&
+ (
+ cd winreflink &&
+ test_commit base file.txt base &&
+ git worktree add --reflink wt -b winbr base 2>err &&
+ test_grep "build 26200" err &&
+ echo base >expect &&
+ test_cmp expect wt/file.txt &&
+ git -C wt status --porcelain >status &&
+ test_must_be_empty status
+ )
+'
+
test_expect_success '--reflink produces a correct checkout on any filesystem' '
test_when_finished "rm -rf reflinkrepo" &&
git init reflinkrepo &&
--
gitgitgadget
next prev parent reply other threads:[~2026-10-21 7:17 UTC|newest]
Thread overview: 21+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-06-05 18:49 [PATCH 0/2] worktree: copy-on-write creation and shared-branch worktrees Jason Newton via GitGitGadget
2026-06-05 18:49 ` [PATCH 1/2] worktree: add --reflink for copy-on-write worktree creation Jason Newton via GitGitGadget
2026-06-05 18:49 ` [PATCH 2/2] worktree: allow sharing a checked-out branch across worktrees Jason Newton via GitGitGadget
2026-06-05 19:59 ` [PATCH 0/2] worktree: copy-on-write creation and shared-branch worktrees brian m. carlson
2026-06-08 14:36 ` Junio C Hamano
2026-10-20 16:04 ` [PATCH v2 0/2] worktree: copy-on-write creation and ephemeral branches Jason Newton via GitGitGadget
2026-10-20 16:04 ` [PATCH v2 1/2] worktree: add --reflink for copy-on-write worktree creation Jason Newton via GitGitGadget
2026-10-20 21:20 ` brian m. carlson
2026-10-21 7:15 ` Jason Newton
2026-10-20 16:04 ` [PATCH v2 2/2] worktree: reap ephemeral branches created by "worktree add" Jason Newton via GitGitGadget
2026-10-21 6:52 ` Eric Sunshine
2026-10-21 7:40 ` Jason Newton
2026-10-21 8:06 ` Eric Sunshine
2026-10-21 8:28 ` Jason Newton
2026-10-21 14:03 ` Phillip Wood
2026-10-21 7:08 ` [PATCH v2 0/2] worktree: copy-on-write creation and ephemeral branches Eric Sunshine
2026-10-21 7:17 ` [PATCH v3 0/4] " Jason Newton via GitGitGadget
2026-10-21 7:17 ` [PATCH v3 1/4] worktree: add --reflink for copy-on-write worktree creation Jason Newton via GitGitGadget
2026-10-21 7:17 ` [PATCH v3 2/4] worktree: reap ephemeral branches created by "worktree add" Jason Newton via GitGitGadget
2026-10-21 7:17 ` Jason Newton via GitGitGadget [this message]
2026-10-21 7:17 ` [PATCH v3 4/4] worktree: keep the reflink probe referenced on Windows Jason Newton via GitGitGadget
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=91806975a6909265b1325206ef2f33a169393920.1792567061.git.gitgitgadget@gmail.com \
--to=gitgitgadget@gmail.com \
--cc=git@vger.kernel.org \
--cc=nevion@gmail.com \
--cc=sandals@crustytoothpaste.net \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox