Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
c439389
ci: run the test suite on Windows
nexxeln Oct 9, 2026
48317b9
fix: canonicalize paths with dunce
nexxeln Oct 9, 2026
c229118
fix: report a workspace that another program is using
nexxeln Oct 9, 2026
e99d9ef
fix(cli): leave a workspace before moving it to trash
nexxeln Oct 9, 2026
9575fda
fix: run Windows hooks through cmd /d /s /c
nexxeln Oct 9, 2026
af79d38
feat(cli): add a PowerShell shell-init wrapper
nexxeln Oct 9, 2026
3086b20
fix: reject Windows device names and forbidden characters
nexxeln Oct 9, 2026
c9b1f16
test: add Windows CLI checks for paths, remove, and pwsh
nexxeln Oct 9, 2026
6b12eae
fix(opencode): spawn rift without pinning a Windows workspace
nexxeln Oct 9, 2026
bbeab76
fix(opencode): find rift.exe beside a global npm shim
nexxeln Oct 9, 2026
fcb2b70
fix(opencode): stop rewriting in_use and cow_unavailable
nexxeln Oct 9, 2026
4b826ff
fix(cli): leave a sibling descendant before trashing it
nexxeln Oct 9, 2026
52f6cb2
feat(windows): probe ReFS block cloning and fail closed elsewhere
nexxeln Oct 9, 2026
fbdc774
ci(windows): test the ReFS strategy on ReFS and Dev Drive volumes
nexxeln Oct 9, 2026
f2f3317
feat(windows): copy metadata, links, streams, and hard links
nexxeln Oct 9, 2026
4c1aeeb
feat(windows): flush sources before cloning them
nexxeln Oct 9, 2026
9db4be0
feat(windows): remove read-only workspaces
nexxeln Oct 9, 2026
5ea565a
test(windows): cover storage on another volume and time a warm create
nexxeln Oct 9, 2026
4d61eb1
test(windows): cover sparse sources in the ReFS round trip
nexxeln Oct 9, 2026
d8453d7
refactor(windows): name retry, stream, and probe constants
nexxeln Oct 9, 2026
d5219da
refactor(windows): make flush outcomes and FFI contracts explicit
nexxeln Oct 9, 2026
bc97e0d
test(windows): assert git refresh and mark gated e2e runs
nexxeln Oct 9, 2026
2d76873
refactor: shorten the Windows reserved-name check
nexxeln Oct 9, 2026
565751c
ci: run the Windows jobs in the main CI workflow
nexxeln Oct 9, 2026
d682696
docs: document Windows support
nexxeln Oct 9, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,75 @@ jobs:
env:
RIFT_REQUIRE_APFS_TESTS: "1"
run: cargo test --package rift --locked

windows-ntfs-test:
name: Windows tests (NTFS)
runs-on: windows-latest
env:
RIFT_REQUIRE_UNSUPPORTED_WINDOWS_TESTS: "1"
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2

- name: Run tests
run: cargo test --workspace --locked --no-fail-fast -- --show-output

refs:
name: Windows tests (ReFS)
runs-on: windows-2022
env:
CARGO_TARGET_DIR: ${{ github.workspace }}\target
RIFT_REQUIRE_REFS_TESTS: "1"
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2

- name: Create a ReFS volume and copy the checkout onto it
run: ./scripts/ci/windows-refs.ps1 -Volume E -CreatePartition

- name: Run tests on ReFS
working-directory: ${{ env.RIFT_CHECKOUT }}
run: cargo test --package rift --package rift-cli --locked --no-fail-fast -- --show-output

devdrive:
name: Windows tests (Dev Drive)
runs-on: windows-2025
env:
CARGO_TARGET_DIR: ${{ github.workspace }}\target
RIFT_REQUIRE_REFS_TESTS: "1"
steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Create a Dev Drive
uses: samypr100/setup-dev-drive@v4
with:
drive-size: 10GB
drive-format: ReFS
native-dev-drive: true
trusted-dev-drive: true

- name: Install Rust toolchain
uses: dtolnay/rust-toolchain@stable

- name: Cache Rust dependencies
uses: Swatinem/rust-cache@v2

- name: Copy the checkout onto the Dev Drive
run: ./scripts/ci/windows-refs.ps1 -Volume $env:DEV_DRIVE

- name: Run tests on the Dev Drive
working-directory: ${{ env.RIFT_CHECKOUT }}
run: cargo test --package rift --package rift-cli --locked --no-fail-fast -- --show-output
9 changes: 9 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ version = "0.0.13"
[workspace.dependencies]
clap = { version = "4.5", features = ["derive"] }
dirs = "6.0"
dunce = "1"
libc = "0.2"
rand = "0.9"
rusqlite = { version = "0.35", features = ["bundled"] }
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ eval "$(rift shell-init zsh)" # or bash
rift shell-init nushell | save -f (($nu.user-autoload-dirs | first) | path join "rift.nu")
```

```powershell
Invoke-Expression (& { (rift shell-init pwsh | Out-String) })
```

## Lifecycle Hooks

Add `.rift.toml` to a workspace to run commands around creation and removal:
Expand Down Expand Up @@ -71,6 +75,8 @@ run = "docker compose -p rift-$RIFT_ID down -v"
Hooks receive `RIFT_SOURCE`, `RIFT_DESTINATION`, `RIFT_ID`, and `RIFT_PARENT_ID`. Hook output goes to stderr so
workspace paths on stdout stay machine-readable. Use `--no-hooks` to skip hooks.

On Windows, hooks run through `cmd.exe`, so write `%RIFT_ID%` instead of `$RIFT_ID`.

## Agents and OpenCode

Each agent can get an isolated copy of your real working state instead of sharing your checkout or starting from a
Expand Down Expand Up @@ -153,7 +159,7 @@ keeps the source directory, removes its `.rift` marker, and trashes registered d
| Linux x64 | btrfs snapshots | `rift init` converts a directory into a subvolume |
| Linux x64 | Native per-file reflinks | XFS and other filesystems with working `FICLONE` |
| macOS arm64 / x64 | APFS `clonefile` | Requires an APFS volume |
| Windows x64 | None | Package is published; workspace creation is unsupported |
| Windows x64 | ReFS block cloning | Per-file `FSCTL_DUPLICATE_EXTENTS_TO_FILE` on a ReFS volume such as a [Dev Drive](https://learn.microsoft.com/windows/dev-drive/). `rift init` refuses NTFS |

Each managed workspace has a `.rift` marker containing its ID. A SQLite registry stores paths, parents, and trash
entries. Default storage is adjacent to the source root:
Expand All @@ -167,6 +173,18 @@ entries. Default storage is adjacent to the source root:
Workspaces never overlap, concurrent creates never delete each other's destinations, and removal is a trash operation
until `rift gc` runs.

## Windows

The project must be on a ReFS volume, and `rift create --into` must point to a directory on the same volume. Rift
clones each file separately, so `rift create` takes longer as the number of files grows.

`rift remove` fails with "another program is using the workspace" while another program has a file in the workspace
open or has its working directory there. Close that program and run `rift remove` again.

`rift create` recreates symbolic links and junctions. Recreating a symbolic link needs Windows Developer Mode or
administrator rights. `rift create` refuses encrypted files and every other kind of reparse point, such as cloud
placeholder files.

## JavaScript API

The package selects a Bun or Node FFI binding through conditional exports.
Expand Down
1 change: 1 addition & 0 deletions crates/cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ path = "src/main.rs"

[dependencies]
clap.workspace = true
dunce.workspace = true
rift = { path = "../core" }
thiserror.workspace = true

Expand Down
Loading
Loading