# Copy strategies and platforms

> Platform-specific copy-on-write backends (btrfs subvolumes, Linux reflinks, APFS clonefile), filtered vs exact copy modes, and unsupported platforms.

- Repository: anomalyco/rift
- GitHub: https://github.com/anomalyco/rift
- Human docs: https://www.grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662
- Complete Markdown: https://www.grok-wiki.com/public/docs/anomalyco-rift-ea3fd5dbf662/llms-full.txt

## Source Files

- `specs.md`
- `README.md`
- `crates/core/src/strategy/mod.rs`
- `crates/core/src/strategy/btrfs.rs`
- `crates/core/src/strategy/reflink.rs`
- `crates/core/src/strategy/apfs.rs`
- `crates/core/src/strategy/linux.rs`

---

---
title: "Copy strategies and platforms"
description: "Platform-specific copy-on-write backends (btrfs subvolumes, Linux reflinks, APFS clonefile), filtered vs exact copy modes, and unsupported platforms."
---

Rift routes workspace copying through a `Strategy` trait in `crates/core/src/strategy/`. `Manager::open` selects a platform backend via `default_strategy()`, then delegates `init`, `create`, and `gc` filesystem work to that implementation. There is no full-byte-copy fallback: if no copy-on-write backend succeeds, `create` fails with `CowUnavailable`.

## Strategy architecture

`Strategy` defines three operations:

| Method | Used by | Default behavior |
| --- | --- | --- |
| `copy_directory(from, to, mode)` | `create` | Platform-specific CoW clone |
| `initialize_directory(path, progress)` | `init` | No-op (`AlreadyNative`) unless the backend converts the workspace |
| `remove_directory(path)` | Failed `create` rollback, `gc` | `fs::remove_dir_all` |

`default_strategy()` resolves at compile time:

| OS target | Backend |
| --- | --- |
| Linux | `LinuxStrategy` (filesystem router) |
| macOS | `ApfsStrategy` |
| Other (e.g. Windows) | `UnsupportedStrategy` |

```mermaid
classDiagram
    class Strategy {
        <<trait>>
        +copy_directory(from, to, mode)
        +initialize_directory(path, progress)
        +remove_directory(path)
    }
    class LinuxStrategy {
        +filesystem detection via statfs
    }
    class BtrfsStrategy {
        +subvolume snapshots
        +reflink import
    }
    class LinuxReflinkStrategy {
        +FICLONE per-file reflinks
    }
    class ApfsStrategy {
        +clonefile syscall
    }
    class UnsupportedStrategy {
        +always CowUnavailable
    }
    Strategy <|.. LinuxStrategy
    Strategy <|.. ApfsStrategy
    Strategy <|.. UnsupportedStrategy
    LinuxStrategy --> BtrfsStrategy : btrfs
    LinuxStrategy --> LinuxReflinkStrategy : other
```

On Linux, `LinuxStrategy` calls `statfs` and compares `f_type` to `BTRFS_SUPER_MAGIC` (`0x9123683e`). Btrfs paths use `BtrfsStrategy`; all other filesystems use `LinuxReflinkStrategy`.

## Platform support matrix

| Platform | Filesystem | `init` behavior | `create` (filtered, default) | `create` (exact, `--copy-all`) |
| --- | --- | --- | --- | --- |
| Linux | btrfs | Convert ordinary directory to subvolume via reflink import, or register if already a subvolume | New subvolume + filtered reflink import | Writable btrfs snapshot (`BTRFS_IOC_SNAP_CREATE`) |
| Linux | XFS and other reflink-capable | Verify `FICLONE` support; register in place | Per-file reflink tree walk with filter | Full per-file reflink clone |
| macOS arm64 / x64 | APFS | Register in place | Per-entry `clonefile` with filter | Directory `clonefile` |
| Windows x64 | — | Registers workspace metadata only if reached | **Not implemented** — `CowUnavailable` | **Not implemented** |

<Note>
The npm package publishes Windows binaries, but workspace creation has no CoW backend. `create` always fails on unsupported platforms.
</Note>

## Copy modes

`CopyMode` controls what `create` materializes. The default is filtered copying.

| Mode | CLI flag | API field | Behavior |
| --- | --- | --- | --- |
| `Filtered` | *(default)* | `copyAll: false` / omitted | Omit heavyweight regenerable artifacts; preserve source manifests, lockfiles, dirty/staged/untracked files, and ignored paths not in the exclusion set |
| `All` | `--copy-all` | `copyAll: true` | Exact tree copy including all dependency and build artifacts |

```bash
rift create                  # filtered (default)
rift create --copy-all       # exact copy
```

```ts
create({ from: "/path/to/workspace", copyAll: true })
```

### Filtered artifact exclusions

`CopyFilter` matches path components at any depth. A path is excluded when any component matches, or when a `.yarn/<artifact>` pair matches.

| Category | Excluded names / paths |
| --- | --- |
| Node / JS | `node_modules`, `.pnpm-store`, `.yarn/cache`, `.yarn/unplugged`, `.yarn/install-state.gz`, `.yarn/build-state.yml`, `.next`, `.nuxt`, `.svelte-kit`, `.turbo`, `.vite`, `.parcel-cache` |
| Rust | `target` |
| Python | `.venv`, `venv`, `.tox`, `.nox`, `__pycache__`, `.pytest_cache`, `.mypy_cache`, `.ruff_cache` |
| Build output | `dist`, `build`, `coverage`, `.cache` |

Filtered mode still copies symlinks, hard links (within the included set), permissions, ownership, timestamps, and extended attributes on included entries.

## Btrfs strategy

`BtrfsStrategy` is the production backend for btrfs workspaces on Linux.

### Subvolume detection

A btrfs path is treated as a subvolume when `metadata(path).ino() == 256`. Ordinary btrfs directories fail `create` with `InitializationRequired` until `rift init` converts them.

### Initialization (`rift init`)

For an ordinary btrfs directory:

1. Create staging subvolume `.rift-init-{ULID}` beside the workspace
2. Reflink-import the full workspace into staging (reports `InitProgress::ImportedEntries`)
3. Move the original aside to `.rift-init-original-{ULID}`
4. Atomically rename staging into the original path
5. Copy metadata and remove the original backup

If the path is already a subvolume, initialization is a no-op (`StrategyInit::AlreadyNative`). If the path is not on btrfs, initialization returns `CowUnavailable`.

The CLI surfaces progress for btrfs conversion: `Creating BTRFS subvolume...`, `Importing workspace...`, then `Ready <path>`.

### Creation

| `CopyMode` | Mechanism |
| --- | --- |
| `All` | `BTRFS_IOC_SNAP_CREATE` writable snapshot from source subvolume |
| `Filtered` | `BTRFS_IOC_SUBVOL_CREATE` on destination, then `import_directory_linux_filtered`, then metadata copy |

### Removal and garbage collection

`remove_directory` attempts `BTRFS_IOC_SNAP_DESTROY` first. On `PermissionDenied` or `Unsupported`, it empties the subvolume contents and removes the directory with ordinary `remove_dir`. Non-subvolume paths use `remove_dir_all`.

## Linux reflink strategy

`LinuxReflinkStrategy` handles non-btrfs Linux filesystems that support the `FICLONE` ioctl (`0x40049409`), including XFS when kernel reflink support succeeds.

### Initialization

`initialize_directory` writes a probe file, attempts a reflink clone, and deletes the probes. Failure produces `CowUnavailable` with a message that the path does not support Linux copy-on-write reflinks. Success returns `AlreadyNative` — no directory replacement.

### Creation

Before copying, Rift verifies:

1. Source and destination parent share the same `dev` (same filesystem)
2. The destination parent passes the reflink probe

The import walk (`WalkDir`, `follow_links(false)`) handles each entry:

| Entry type | Action |
| --- | --- |
| Directory | `create_dir`, metadata copied bottom-up |
| Regular file | `FICLONE` reflink; hard links deduplicated via `(dev, ino)` map |
| Symlink | Recreated with `symlink`; metadata on symlink target |
| Other (FIFO, device, etc.) | `UnsupportedEntry` |

Both `All` and `Filtered` modes use the same reflink machinery; filtered mode applies `CopyFilter` during the walk.

### Removal

Non-btrfs paths use `fs::remove_dir_all`.

<Warning>
Reflink cloning requires source and destination on the same filesystem. If the registered source workspace is a mount root, the default sibling `.rifts/` storage may sit on a different filesystem and CoW will fail. Pass `--into` (or `into` in the API) on a path colocated with the source.
</Warning>

## APFS strategy (macOS)

`ApfsStrategy` uses the `clonefile(2)` syscall for CoW cloning.

| `CopyMode` | Mechanism |
| --- | --- |
| `All` | Single `clonefile` on the source directory tree |
| `Filtered` | Create destination root, walk included entries, `clonefile` per file, recreate symlinks and hard links, copy metadata |

Initialization uses the default `Strategy` implementation — APFS workspaces register in place with no conversion step.

Removal uses `fs::remove_dir_all`.

## Unsupported platforms

On targets other than Linux and macOS, `UnsupportedStrategy` implements `copy_directory` by returning:

```
copy-on-write cloning unavailable: no copy-on-write strategy has been implemented for this platform
```

`initialize_directory` still returns `AlreadyNative`, so `init` can register metadata, but `create` cannot produce a child workspace. Rift does not fall back to `fs::copy` or external copy commands.

## Error codes and CLI messages

Strategy failures surface through the core `Error` enum and FFI `RiftError` codes:

| Error variant | FFI code | Typical cause |
| --- | --- | --- |
| `CowUnavailable` | `cow_unavailable` | Unsupported platform, missing reflink/FICLONE, cross-filesystem destination, btrfs ioctl failure |
| `InitializationRequired` | `initialization_required` | `create` on btrfs ordinary directory that was never converted by `rift init` |
| `UnsupportedEntry` | `unsupported_entry` | FIFO, device node, or other non-file/dir/symlink in the tree |

CLI user-facing overrides:

| Error | CLI message |
| --- | --- |
| `InitializationRequired` | `this workspace must be initialized first; run rift init from its root folder` |
| `CowUnavailable` | Full error string via `error.to_string()` (prefix: `copy-on-write cloning unavailable:`) |

On copy failure, `Manager::create_with_options` removes a partially created destination directory via `strategy.remove_directory` before returning the error.

## CoW lifecycle by operation

```text
init
  Linux btrfs (ordinary dir)  →  reflink import into new subvolume, atomic swap
  Linux btrfs (subvolume)     →  register only
  Linux other (reflink OK)    →  probe FICLONE, register only
  macOS APFS                  →  register only
  Windows                     →  register only (create will still fail)

create (filtered)
  btrfs   →  new subvolume + filtered reflink import
  reflink →  filtered per-file FICLONE walk
  APFS    →  filtered per-entry clonefile walk

create (--copy-all)
  btrfs   →  writable subvolume snapshot
  reflink →  full per-file FICLONE clone
  APFS    →  directory clonefile

gc / remove rollback
  btrfs subvolume  →  SNAP_DESTROY, or empty + rmdir fallback
  other            →  remove_dir_all
```

## Verification signals

Use these observable outcomes to confirm the active backend:

| Signal | Backend |
| --- | --- |
| `Creating BTRFS subvolume...` during `rift init` | Btrfs conversion in progress |
| `Ready <path>` after first btrfs `init` | Subvolume conversion completed |
| `create` completes in sub-second time on large trees | CoW backend active (snapshot, reflink, or clonefile) |
| `copy-on-write cloning unavailable` on `create` | Platform unsupported, reflink probe failed, or cross-filesystem `--into` |
| `initialization_required` / CLI init hint | Btrfs source not yet converted to subvolume |
| `unsupported_entry` with a path | Tree contains a non-cloneable entry type (e.g. FIFO) |

Integration tests gate on environment variables: `RIFT_REQUIRE_BTRFS_TESTS`, `RIFT_REQUIRE_REFLINK_TESTS`, and `RIFT_REQUIRE_APFS_TESTS` assert the corresponding backends are available in CI.

## Related pages

<CardGroup>
  <Card title="Initialize a workspace" href="/initialize-workspace">
    Btrfs subvolume conversion, reflink verification during init, and progress signals.
  </Card>
  <Card title="Create workspaces" href="/create-workspaces">
    `--copy-all`, `--into`, and filtered vs exact copy from the CLI perspective.
  </Card>
  <Card title="Installation" href="/installation">
    Platform support matrix and prebuilt binary targets.
  </Card>
  <Card title="Error codes" href="/error-codes">
    Full `RiftError` catalog including `cow_unavailable` and `initialization_required`.
  </Card>
  <Card title="Troubleshooting" href="/troubleshooting">
    CoW unavailable failures, cross-filesystem storage, and platform limitations.
  </Card>
</CardGroup>
