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>
Subject: [PATCH v3 0/4] worktree: copy-on-write creation and ephemeral branches
Date: Wed, 21 Oct 2026 07:17:37 +0000	[thread overview]
Message-ID: <pull.2317.v3.git.git.1792567061.gitgitgadget@gmail.com> (raw)
In-Reply-To: <pull.2317.git.git.1780685368.gitgitgadget@gmail.com>

When many worktrees share one repository -- for example a fleet of agents
each needing an isolated checkout -- "git worktree add" is costly at scale.
Objects are shared via the common directory, but the working tree is not:
each add rewrites every tracked file, so N worktrees cost N full checkouts
of disk and I/O. A branch created only to back one of those worktrees is
also left behind when the worktree is removed.

Patch 1 adds "git worktree add --reflink". On a copy-on-write filesystem it
populates the new worktree by reflinking the current worktree's files and
index, then "git reset --hard" rewrites only the paths that differ from .
reflink_file() in copy.c uses FICLONE (Linux), clonefile() (macOS), and ReFS
block cloning (FSCTL_DUPLICATE_EXTENTS_TO_FILE, as Git LFS does). Elsewhere
it is probed up front and falls back to a normal checkout. worktree.reflink
(true/false/auto) sets the default; --no-reflink overrides.

Uncommitted edits to tracked files in the source do not leak into the new
worktree. The copied index is refreshed before reset, and because every
reflinked file is a new inode the refresh re-validates by content. Only
untracked files are carried over. The option stays opt-in for that reason:
it copies the source's untracked and ignored files, which a plain checkout
does not.

For many worktrees at one commit, the series does not share a branch.
git-worktree(1) shows a single update-ref transaction that creates a ref per
worktree (or a detached HEAD). A shared branch still lets a stale index in
one worktree silently revert a commit made in another.

Patch 2 treats a branch that "git worktree add " created itself as owned by
that worktree. "git worktree remove" deletes it. Branches named with -b/-B,
or that already existed, are left alone. --keep-branch keeps an owned
branch.

Changes since v1:

 * Dropped the patch that allowed one branch to be checked out in several
   worktrees. Documented the per-ref / detached-HEAD pattern instead (brian
   m. carlson, Junio).
 * Explained, in the commit message and the manual, why a dirty source
   worktree does not leak tracked content (brian m. carlson).
 * Added the ReFS block-cloning arm (brian m. carlson).
 * Noted overlayfs as a supported setup when the lower filesystem supports
   reflinks (brian m. carlson).
 * Kept --reflink opt-in. A whole-tree reflink is not invisible: it carries
   untracked files, and cloning every file can be slower than checkout when
   the files are small (Junio; qub1n on the ReFS work).
 * Rebased onto 47fdf2208c ("The 7th batch"). reflink_file() now takes a
   repository argument, matching copy_file().
 * Added ephemeral-branch reaping for the auto-named branch that "worktree
   add " creates.

ReFS measurements (32 GiB Dev Drive, Windows build 26300). The USB disk
itself would not format as ReFS, so the volume is a Dev Drive VHD on an NTFS
partition of that disk. NTFS rejects the clone ioctl (win32 error 1 there,
error 50 on C:) and falls back to a full write.

The clone shares the source clusters and does not flush. Four thousand
clones of one 8 MiB file, 31.3 GiB of logical names, were read back
unbuffered and SHA-256'd in full: 4000 of 4000 matched the source. The
source was then overwritten with a different pattern. A read-only
FlushFileBuffers failed with win32 error 5; a writable reopen succeeded.
After that flush, 4000 of 4000 clones still hashed to the original contents
and none hashed to the new source. While the clones were created the
free-space counter fell to about 1 GiB, then returned to 28.38 GiB with the
clones still present. The backing VHD did not grow. Deleting the clones
freed nothing further.

Jason Newton (4):
  worktree: add --reflink for copy-on-write worktree creation
  worktree: reap ephemeral branches created by "worktree add"
  worktree: disable reflink on Windows
  worktree: keep the reflink probe referenced on Windows

 Documentation/config/worktree.adoc |  12 ++
 Documentation/git-worktree.adoc    |  75 ++++++-
 builtin/branch.c                   |  28 +++
 builtin/worktree.c                 | 315 ++++++++++++++++++++++++++++-
 copy.c                             |  83 ++++++++
 copy.h                             |  14 ++
 t/t2400-worktree-add.sh            | 166 +++++++++++++++
 t/t3200-branch.sh                  |   2 +-
 t/t3400-rebase.sh                  |   2 +-
 t/t5516-fetch-push.sh              |   2 +-
 10 files changed, 690 insertions(+), 9 deletions(-)


base-commit: 47fdf2208c5b94c8cc01c106b8f7c85efb91c926
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2317%2Fnevion%2Fworktree-reflink-cow-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2317/nevion/worktree-reflink-cow-v3
Pull-Request: https://github.com/git/git/pull/2317

Range-diff vs v2:

 1:  83841020f4 ! 1:  a163f737af worktree: add --reflink for copy-on-write worktree creation
     @@ Commit message
          and bcachefs implement, and the one overlayfs forwards when the lower
          filesystem can clone. This path has been tested on btrfs.
      
     -    On macOS it uses clonefile(), the APFS primitive. That path is
     -    implemented and has not been run here.
     +    On macOS it uses clonefile(), the APFS primitive. The t2400
     +    REFLINK prerequisite probes that with "cp -c", because BSD cp has no
     +    --reflink=always, so the macOS CI jobs exercise this path on APFS.
      
          On Windows it uses ReFS block cloning,
          FSCTL_DUPLICATE_EXTENTS_TO_FILE, as Git LFS does. The destination is
     @@ t/t2400-worktree-add.sh: test_expect_success 'relative worktree sets extension c
      +# Reflinks are only available on copy-on-write filesystems (btrfs, XFS,
      +# bcachefs, APFS, ...). Where they are unavailable, --reflink transparently
      +# falls back to a regular checkout, which the test above already covers.
     ++#
     ++# GNU cp probes with --reflink=always. macOS cp has no such option; -c
     ++# is clonefile(2), which is what the APFS CI jobs exercise.
      +test_lazy_prereq REFLINK '
      +	echo probe >reflink-src &&
     -+	cp --reflink=always reflink-src reflink-dst 2>/dev/null
     ++	if test_have_prereq MACOS
     ++	then
     ++		cp -c reflink-src reflink-dst 2>/dev/null
     ++	else
     ++		cp --reflink=always reflink-src reflink-dst 2>/dev/null
     ++	fi
      +'
      +
      +test_expect_success REFLINK '--reflink carries untracked files and reconciles changes' '
 2:  6e2acdc5e2 = 2:  042485f0e9 worktree: reap ephemeral branches created by "worktree add"
 -:  ---------- > 3:  91806975a6 worktree: disable reflink on Windows
 -:  ---------- > 4:  876fb541a8 worktree: keep the reflink probe referenced on Windows

-- 
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 ` Jason Newton via GitGitGadget [this message]
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   ` [PATCH v3 3/4] worktree: disable reflink on Windows Jason Newton via GitGitGadget
2026-10-21  7:17   ` [PATCH v3 4/4] worktree: keep the reflink probe referenced " 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=pull.2317.v3.git.git.1792567061.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