Git development
 help / color / mirror / Atom feed
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


  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