Skip to content

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+.