Data Flow
This page traces the major commands through the layers. Each flow is the same shape: parse args → load config → load identities → read source → build target → compare with destination and database → resolve conflicts → write → update database.
guisu apply
The core command. Materialises source into destination. The flow is:
- Parse CLI args —
--interactive,--dry-run,--include,--exclude, source/destination overrides. - Load
.guisu.toml+ any platform-specific variable files; merge them. - Load age identities from the configured identity files (age keys or SSH keys).
- Build the template engine and a context populated with system info, guisu metadata, and user variables.
- Read
SourceState— walk the source directory in parallel via rayon; parse file attributes from each filename; buildSourceEntryobjects. - Build
TargetState— for each source entry, decrypt.agefiles and render.j2templates, again in parallel. - Open the redb database at
<source>/.guisu-state.db. - For each entry in the target state (sequential, so writes are deterministic):
- Read the corresponding
DestinationStateentry (the actual file on disk). - Load the database entry (the last applied hash + mode).
- Three-way compare the three to compute a
FileStatus. - Resolve the status:
Synced— skip.Added/Modified— write the target content with the target mode.Conflict— if--interactive, open the TUI; otherwise overwrite.- User can also
Quitfrom the TUI, which aborts the entire apply.
- Update the database with the new hash and mode.
- Read the corresponding
- Show stats — counts of added / modified / skipped / errored entries.
Steps 5 and 6 use rayon for parallel processing. Steps 8 and 9 are sequential so writes happen in a deterministic order and a mid-apply crash leaves the destination in a recoverable state.
guisu init
- Parse the target — a local path, a GitHub username, or
owner/repo. - Determine the source directory (default
~/.local/share/guisu). - If the source directory already exists, error out.
- Clone the repository (in-process via the
git2crate). - Run
guisu applyin interactive mode so the user can review the changes before they land.
guisu add
- Resolve the path: expand
~, make it absolute, verify it exists. - Compute the target path: strip the
$HOMEprefix. - If
--encryptwas passed, encrypt the content and append.ageas the suffix.--templateinstead appends.j2. Otherwise the file is used as-is. - Copy the (possibly transformed) file to the source directory, preserving metadata.
- Run
git addon the resulting source path.
--private and --executable do not change the file’s location, only the attributes Guisa applies on the next apply.
guisu update
- Open the source repo via
git2. git fetch origin.- If
--rebase, rungit rebase; otherwisegit merge. - If there are conflicts, error out (the user must resolve them manually).
- Run
guisu apply.
guisu edit
- Map the destination path back to the source path (look for
.j2/.agesuffixes). - If the source is an
.agefile, decrypt to a temp file (mode0600). - Open the source (or temp) file in
$EDITORand wait for it to exit. - If the content’s hash changed, and the source is
.age, re-encrypt and replace the source. Otherwise just replace the source. - Delete the temp file.
The temp file lives on the same filesystem as the source, with mode 0600, and is unlink()-ed when the editor exits. Secure erasure (overwrite before unlink) is on the roadmap but not implemented.
Parallel processing
The engine uses rayon for two passes:
| Pass | Operation | What parallelises |
|---|---|---|
| Read source | SourceState::read(root) | One task per file system entry: read bytes, parse attributes, build SourceEntry. |
| Build target | TargetState::from_source(source, processor, context) | One task per source entry: decrypt (if .age), render template (if .j2), build TargetEntry. |
Sequential steps (write, update db, compare) stay single-threaded to keep the on-disk state coherent and the diff output stable.
See also
- Three-State Model — the four stores the flows above touch.
- Crates — guisu-engine — the types in the flow.