Files
Maximilian Roos acc0fd06d8 Report whether copy-ignored reflinked or copied in full (#4025)
`wt step copy-ignored` told every user the same thing — `Copied 65,679
files · 29.5 GB` — whether the filesystem shared the source's blocks or
wrote all 29.5 GB out. On ext4 and NTFS, which have no reflink, that is
the difference between a free copy and a full one, and nothing in the
output said which had happened. #4022 documented the caveat per
filesystem; this reports it per machine.

```
✓ Copied 4,812 files · 14.0 GB (reflinked, no extra disk)
✓ Copied 4,812 files · 14.0 GB (full copy)
✓ Copied 4,812 files · 14.0 GB (3,200 of 4,812 reflinked)
```

The signal was already in hand and thrown away. `reflink_or_copy`
returns `Ok(None)` when the platform's clone syscall succeeded and
`Ok(Some(bytes))` when it fell through to `fs::copy`, and `copy_leaf`
matched `Ok(_)`. It now returns that alongside the byte count,
`Progress::record` takes a `DataCopy` and counts each side,
`Progress::copy_split` reads the pair back, and `--format=json` gains
`reflinked` and `written` — with one rule and no exceptions: a payload
reporting a **result** carries all four, zeroed where nothing was
copied, and a **plan** (`dry_run: true`) carries none of them, since it
says what would be copied rather than what was. That last half is a
small behaviour change beyond the new keys — the `--require-include` and
empty-entries returns fire before the dry-run branch, so under
`--dry-run` they used to emit `files: 0, bytes: 0` while a plan with
entries in it emitted neither. They now emit none, so `jq '.reflinked +
.written'` no longer returns a number for one plan and null for another.
`same_worktree` is unchanged: it reports a result rather than a plan and
carries no `dry_run` key.

Only the reflinked wording carries a gloss, since that is the term a
reader won't know; the contrast then says what a full copy cost without
repeating the byte count sitting two words to its left.

<details>
<summary>Three decisions worth a look</summary>

**Symlinks record `DataCopy::Neither` rather than counting as written.**
A symlink's content is a path, so it has no extents to share or to
write. Counting it on either side would make a `node_modules/` full of
bin shims report as a partial reflink failure on a machine where
everything with data in it cloned fine.

**`WORKTRUNK_TEST_REFLINK=1|0` pins the reported label**, while the copy
still attempts a reflink either way. Whether a clone succeeds is a
property of the filesystem under the test's temp directory, and CI spans
APFS, ext4, and NTFS, so no one snapshot could hold on all three and the
branch a given run took would be invisible. It is set per command in the
copy-ignored tests rather than in `STATIC_TEST_ENV_VARS`, which reaches
every child and would add an `env:` line to every snapshot in the suite.
Eleven snapshots pin the reflinked branch and one new test pins `(full
copy)`; the mixed case needs two filesystems under one tree, so unit
tests cover its counting and its rendering.

**`classify_copy` is a named function for the same reason.** The
coverage job runs on `ubuntu-24.04`, so ext4 never reaches the reflinked
arm and it would have posted as a patch miss. A unit test pins the
mapping instead of whichever runner happens to execute it.

</details>

The signal is exact per file, but it says only that a clone did not
happen, never why — an unsupported filesystem and a cross-device copy
are indistinguishable here. That is enough for the claim the output
makes (those bytes really were written) and not enough to assert "this
filesystem has no reflink", which the wording avoids.

`wt step promote` also calls `copy_leaf` and reports no split: its copy
path only runs as the cross-device fallback when `rename` fails with
EXDEV, where a reflink is impossible by definition.

> _This was written by Claude Code on behalf of max-sixty_

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01NrPJmECkKALUaqxb9GKCC5

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-06 20:24:22 -07:00
..