CLI commands¶
Every command accepts -C <path> to operate on another repo without cd.
| Command | Purpose |
|---|---|
forest init |
Create the workspace container, or report setup status if it exists. |
forest checkout <name> |
Switch checkouts, creating + registering + activating first if the name is new. |
forest checkout create/list/current/remove <name> |
Explicit checkout management; remove --yes skips the prompt for scripts. |
forest add STAGE PATH [--sync-by MODE] |
Register a new stage and bind it to a local path; --sync-by picks unit discovery (subdirectory / directory / file). |
forest bind [STAGE PATH] / forest unbind STAGE |
Manage local stage↔path bindings. |
forest remote add/remove/list/use/show |
Manage remotes; use selects the active remote (optional while only one exists). |
forest push / pull / status / diff / ls |
Sync and inspect against the active remote. Bare invocations cover every bound stage (unbound stages warn and skip); --all requires all stages bound. Target slices with positional UNIT_IDS + --stage, or --id via the manifest; push/pull accept --dry-run and --no-verify, and push accepts repeatable unit-relative --exclude globs. push --force overwrites remote files that changed since the last recorded sync; pull --force overwrites local files that differ from it. status --checksum re-hashes local contents (slower; catches same-size edits). ls adds --tree and --long. |
forest flow |
Emit a Mermaid data-flow diagram of the active checkout (dry-run only). --id/--stage expand a slice to units and resolved paths; --direction LR\|TD picks the layout; --output PATH writes to a file. |
forest prompt init bash\|zsh |
Print an eval-able snippet that shows the active checkout in your shell prompt — see the shell prompt guide. |
File selection during sync¶
Forest syncs all files in a data unit, skipping OS junk (.DS_Store,
AppleDouble ._*, *.tmp). It applies no content-based include/exclude
rules.
Integrity & safety¶
Every transfer records per-file SHA-256 content hashes in the sync state
(ADR 0017), so push detects
same-size edits and re-pushes instead of skipping. status stays fast and
offline by comparing paths and sizes; status --checksum re-hashes local
contents against the recorded hashes.
Before downloading, pull asks the backend for content hashes for that unit
and derives a stable fingerprint from each file's path, size, hash algorithm,
and digest (ADR 0023). The last sync
entry first proves the local copy is clean and the remote path is unchanged;
forest then compares a fresh local fingerprint with the live remote
fingerprint. The same decision applies under --dry-run. A changed file or
file set pulls normally; if any source hash is unavailable, forest transfers
rather than guessing. pull --force still performs the transfer.
Verification is on by default
(ADR 0019). After every
transfer, push and pull check the transferred files against the remote
before recording anything — the order is transfer → verify → record. A
mismatch prints ERROR <unit> verification failed: <detail>, exits 1, and
records no sync entry; re-run to retransfer. --no-verify opts out per
invocation. Skipped (SKIP) and --dry-run units receive no post-transfer
verification. Renamed/remapped keys use the strongest available SHA-256,
SHA-1, or MD5 hash; when none is available, verification falls back to size.
Pulls are atomic and guarded
(ADR 0018). Files download to a
temporary .forest-tmp name and are atomically renamed into place — a pull
killed mid-transfer leaves the old bytes or the new bytes, never a
truncated file. A local file whose contents differ from the last recorded
sync refuses with
ERROR <unit> local file has uncommitted changes: <file> (use --force to overwrite)
and exit 1; pull --force overrides. The check also runs under --dry-run.
Pushes take a lease on the remote
(ADR 0020). Before overwriting an
existing remote file, push compares it against the last recorded sync
snapshot; a file that changed out-of-band refuses with
remote changed since last sync: <key>, and a first push into an occupied
prefix refuses with remote already has '<key>' with no recorded sync
history. push --force overrides both — it never re-pushes
already-synced content.
Transfers run under the per-checkout lock, so concurrent pushes of the same checkout serialize per unit instead of racing.
Requirements¶
Transfers require rclone on $PATH; Python 3.9+.