Skip to content

Attempt to use reflinks by default on Linux#18117

Merged
zanieb merged 10 commits intomainfrom
claude/add-linux-reflink-support-Jerha
Feb 23, 2026
Merged

Attempt to use reflinks by default on Linux#18117
zanieb merged 10 commits intomainfrom
claude/add-linux-reflink-support-Jerha

Conversation

@zanieb
Copy link
Member

@zanieb zanieb commented Feb 20, 2026

Copy of #17753 which GitHub auto-closed.

This adds test infrastructure for cross-device links and file systems with reflink support. In short, we create a few extra file systems on the CI runners then provide their paths to the test suite using environment variables to ensure we have coverage. If the variables are not set, the tests are skipped.

claude and others added 2 commits February 20, 2026 08:09
On Linux, default to `LinkMode::Clone` (reflink) instead of
`LinkMode::Hardlink`. The clone fallback chain is now:
Clone -> Hardlink -> Copy, so on filesystems that don't support
reflinks (e.g., ext4), we try one reflink, fail, then fall back to
hardlinks — which is what we were doing before. The overhead of the
single failed FICLONE ioctl is negligible (~80ms in benchmarks).

On filesystems that support reflinks (btrfs, XFS, bcachefs), users
get copy-on-write benefits automatically without needing to opt in.

https://claude.ai/code/session_015S1a39fWo8vqMHhjUe8uoG
Replace UV_INTERNAL__TEST_EXPECT_REFLINK and UV_INTERNAL__TEST_DIR usage
in link tests with two new env vars:

- UV_INTERNAL__TEST_REFLINK_FS: path on a reflink-capable filesystem
- UV_INTERNAL__TEST_TMP_FS: path on a non-reflink filesystem

This enables comprehensive test coverage:
- Same-device reflink (both dirs on reflink fs)
- Same-device fallback (both dirs on non-reflink fs)
- Cross-device clone fallback (reflink fs -> tmpfs and vice versa)
- Cross-device hardlink fallback (must fall back to copy)
- Cross-device symlink (should work across devices)
- Cross-device copy (sanity check)

The Linux CI job now creates both a btrfs loopback and a tmpfs mount.
The macOS CI job sets TEST_REFLINK_FS to the runner temp (APFS).
@zanieb zanieb added the test:macos Enable macOS tests for a pull request label Feb 20, 2026
@zanieb zanieb force-pushed the claude/add-linux-reflink-support-Jerha branch from 1c3d5fa to 6038865 Compare February 20, 2026 14:21
@zanieb zanieb force-pushed the claude/add-linux-reflink-support-Jerha branch from 6038865 to 529ce94 Compare February 20, 2026 14:39
- Remove redundant test_reflink_fails_on_tmp_fs (assertion belongs in tests using nocow fs)
- Rename test_clone_fallback_on_tmp_fs -> test_clone_fallback_on_nocow_fs
- Simplify cross-device tests: use alt_tempdir + test_tempdir instead of cow+alt pairs
- Merge bidirectional cross-device test pairs into single tests
- All cross-device assertions now expect exactly Copy (not Hardlink || Copy)
- Symlink cross-device asserts exactly Symlink
- Remove 'sanity check' from doc comment
- Restructure pip_install tests: install_cross_device, install_copy_on_write_fs, install_no_copy_on_write_fs
- Tests use default link mode instead of explicit --link-mode
- Show fallback warning in cross-device test instead of filtering it
- Add with_cache_on_nocow_fs and with_working_dir_on_nocow_fs test helpers
- Update all documentation to reflect clone default on macOS and Linux
- Fix prettier formatting in docker.md
…able

- with_cache_on_fs/with_working_dir_on_fs now accept a name parameter
- Filters now produce [ALT_FS]/[CACHE_DIR]/, [COW_FS]/[TEMP_DIR]/, etc.
- Inline the canonical variable in with_working_dir_on_fs
@zanieb zanieb marked this pull request as ready for review February 20, 2026 16:23
@zanieb zanieb added the enhancement New feature or improvement to existing functionality label Feb 20, 2026
- install_cross_device_explicit_copy: --link-mode copy suppresses the warning
- install_cross_device_symlink: symlinks work across devices without warning
@zanieb zanieb requested a review from EliteTK February 20, 2026 16:56
Copy link
Contributor

@EliteTK EliteTK left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seams neat, I assume I am understanding it correctly that if I'm on Linux on ext4, this won't start complaining about the inability to reflink when a hardlink succeeds instead right?


- name: "Create HFS+ RAM disk (no reflink support)"
run: |
RAMDISK=$(hdiutil attach -nomount ram://524288 | xargs)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any particular reason why it is a ramdisk for HFS+ and a file for BTRFS? I don't think it matters, was just curious.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No particular reason, just agent slop — I'll move it to a file if it's easy.

@zanieb
Copy link
Member Author

zanieb commented Feb 23, 2026

I assume I am understanding it correctly that if I'm on Linux on ext4, this won't start complaining about the inability to reflink when a hardlink succeeds instead right?

It shall not

@zanieb zanieb enabled auto-merge (squash) February 23, 2026 13:51
@zanieb zanieb merged commit 7ce61d5 into main Feb 23, 2026
126 of 127 checks passed
@zanieb zanieb deleted the claude/add-linux-reflink-support-Jerha branch February 23, 2026 13:53
zanieb added a commit that referenced this pull request Feb 24, 2026
Fixes an regression from #18117 where executable permissions were not
preserved on reflink. On Linux, the `FICLONE` ioctl only clones data
blocks without preserving file metadata, so permissions must be copied
separately. On macOS, `clonefile` already preserves permissions.

This appears to be a well known issue:

- pnpm/pnpm#8546
-
https://github.com/morelj/reflink/blob/53408edf3bf3c5090b1146923f72066c7f6e9200/cloneflags.go#L6-L22
- python/cpython#81338

Closes #18181
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Feb 25, 2026
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [uv](https://github.com/astral-sh/uv) | patch | `0.10.4` → `0.10.6` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>astral-sh/uv (uv)</summary>

### [`v0.10.6`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0106)

[Compare Source](astral-sh/uv@0.10.5...0.10.6)

Released on 2026-02-24.

##### Bug fixes

- Apply lockfile marker normalization for fork markers ([#&#8203;18116](astral-sh/uv#18116))
- Fix Python version selection for scripts with a `requires-python` conflicting with `.python-version` ([#&#8203;18097](astral-sh/uv#18097))
- Preserve file permissions when using reflinks on Linux ([#&#8203;18187](astral-sh/uv#18187))

##### Documentation

- Remove verbose documentation from optional dependencies help text ([#&#8203;18180](astral-sh/uv#18180))

### [`v0.10.5`](https://github.com/astral-sh/uv/blob/HEAD/CHANGELOG.md#0105)

[Compare Source](astral-sh/uv@0.10.4...0.10.5)

Released on 2026-02-23.

##### Enhancements

- Add hint when named index is found in a parent config file ([#&#8203;18087](astral-sh/uv#18087))
- Add warning for `uv lock --frozen` ([#&#8203;17859](astral-sh/uv#17859))
- Attempt to use reflinks by default on Linux ([#&#8203;18117](astral-sh/uv#18117))
- Fallback to hardlinks after reflink failure before copying ([#&#8203;18104](astral-sh/uv#18104))
- Filter `pylock.toml` wheels by tags and `requires-python` ([#&#8203;18081](astral-sh/uv#18081))
- Validate wheel filenames are normalized during `uv publish` ([#&#8203;17783](astral-sh/uv#17783))
- Fix message when `exclude-newer` invalidates the lock file ([#&#8203;18100](astral-sh/uv#18100))
- Change the missing files log level to debug ([#&#8203;18075](astral-sh/uv#18075))

##### Performance

- Improve performance of repeated conflicts with an extra ([#&#8203;18094](astral-sh/uv#18094))

##### Bug fixes

- Fix `--no-emit-workspace` with `--all-packages` on single-member workspaces ([#&#8203;18098](astral-sh/uv#18098))
- Fix `UV_NO_DEFAULT_GROUPS` rejecting truthy values like `1` ([#&#8203;18057](astral-sh/uv#18057))
- Fix iOS detection ([#&#8203;17973](astral-sh/uv#17973))
- Propagate project-level conflicts to package extras ([#&#8203;18096](astral-sh/uv#18096))
- Use a global build concurrency semaphore ([#&#8203;18054](astral-sh/uv#18054))

##### Documentation

- Update documentation heading for environment variable files ([#&#8203;18122](astral-sh/uv#18122))
- Fix comment about `uv export` formats ([#&#8203;17900](astral-sh/uv#17900))
- Make it clear that Windows is supported in user- and system- level configuration docs ([#&#8203;18106](astral-sh/uv#18106))

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4zMS4xIiwidXBkYXRlZEluVmVyIjoiNDMuMzEuOSIsInRhcmdldEJyYW5jaCI6Im1haW4iLCJsYWJlbHMiOlsiUmVub3ZhdGUgQm90IiwiYXV0b21hdGlvbjpib3QtYXV0aG9yZWQiLCJkZXBlbmRlbmN5LXR5cGU6OnBhdGNoIl19-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or improvement to existing functionality test:macos Enable macOS tests for a pull request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants