Files
IronStorage/docs/git-synchronization.md

5.7 KiB

Embedded Git and synchronization

All Git behavior is implemented in crates/storage. IronStorage never launches git, a credential helper, an SSH client, a hook, a filter, or a merge driver. Repositories are opened with isolated configuration and environment access; repository-local configuration that can affect an operation is rejected. Upstream pass git init deliberately writes diff.gpg.binary and a diff.gpg.textconv GnuPG command. IronStorage preserves these passive keys for compatibility but never evaluates them: it reads Git blobs and renders decrypted diffs through Rust storage APIs. Frontends cannot add or change diff drivers. This behavior follows the upstream password-store initialization while keeping runtime helper execution disabled.

GitRepository initializes and opens password-store worktrees, selects the innermost repository for a nested entry, and implements status, log, diff, add, commit, remote, and local-config operations. Successful insert, edit, generate, recipient-policy, remove, move, and copy transactions use the same concrete committer. Only their affected paths are staged, unrelated index state is preserved, no-op mutations create no commit, and commit failures restore the index so the storage transaction can roll back its files.

Remote endpoint contract

Storage parses credential-free HTTPS URLs plus feature-gated ssh:// and scp-like SSH URLs into one typed endpoint contract. Local paths, git://, file://, helper transports, URL rewrites, separate push URLs, embedded credentials, and unknown schemes are rejected before transport. A build without the ssh feature reports SSH as unsupported before connection or repository mutation instead of treating its configuration as malformed.

HTTPS transport

HTTPS credentials are requested with the configured server ID and application ID and remain outside Git configuration.

Fetch uses the embedded Rust smart-HTTP client with an explicit credential callback, so Git's credential cascade is never entered. Push implements the receive-pack protocol directly: it validates the advertisement, checks the remote tip is an ancestor, creates a complete Git pack with a SHA-1 trailer, requests report-status, and accepts the update only after both unpack and ref status succeed. HTTP redirects are disabled so authorization cannot cross an origin boundary.

SSH authentication

With the optional ssh feature, storage opens an in-process Russh client and verifies the server key before any authentication or command. Known-host files are read with size and line bounds; exact, hashed, and bracketed non-default port entries are supported. Unknown keys require an explicit call to persist the confirmed key atomically. Changed keys always fail and are never replaced.

Authentication uses one configured OpenSSH private-key file (Ed25519, ECDSA, or RSA) or one exact SHA-256 fingerprint from an already-running SSH agent. Encrypted-key passphrases come from SecretBytes in the operating-system secret store. Identity attempts are bounded and deterministic; IronStorage does not spray keys, use passwords or keyboard-interactive authentication, read OpenSSH configuration, start/probe an agent process, or run proxy/helper commands. SHA-1 host signatures and ssh-rsa authentication are excluded. Cancellation interrupts connection and authentication without changing Git, known hosts, or secure storage.

SSH upload-pack

With the same feature enabled, branch discovery, clone, fetch, and pull open a session channel without a PTY and request only git-upload-pack '<path>'. Storage single-quotes the repository as one shell argument, preserving the absolute, account-relative, and tilde path semantics of the two supported SSH URL forms while preventing whitespace, quotes, metacharacters, environment assignments, or options from selecting another command. No local Git, SSH, shell, transport helper, or fetch-pack process is involved.

The channel is a bounded adapter into gix's existing pack-protocol client. Stdout carries only protocol bytes; stderr is retained only as a bounded, single-line remote-service diagnostic. Storage requires a zero exit status and clean channel completion, distinguishes malformed advertisements and packs from network, trust, authentication, service, and cancellation failures, and closes the channel promptly when cancellation is requested. Gix continues to own pack verification, object limits, and atomic ref updates, while the existing IronStorage code continues to own clone staging, checkout, merge, conflict, and rollback behavior.

Pull refuses a dirty worktree. It fast-forwards when possible and otherwise uses the embedded three-way tree merge. Unresolved paths are returned as typed MergeConflicts; no conflict markers or partial checkout are written. Checkout prevalidates tree entries, rejects links and submodules, writes private files atomically, updates the real Git index, and rolls the worktree/index back if the reference update fails. sync performs pull before push, while clone builds in a private sibling directory and installs the completed vault with a rename.

Commit signing is optional. The embedded OpenPGP key store signs the canonical unsigned commit bytes and adds an ASCII-armored gpgsig header compatible with Git/GPG without invoking gpg.

The transport boundary is injectable for deterministic compatibility tests. HTTPS tests inspect credentials, advertisements, receive-pack commands, object counts, pack checksums, non-fast-forward behavior, and server status. SSH tests use a pure-Rust in-process Russh server and upload-pack fixture to exercise chunked reference and pack streams, end-to-end clone/fetch/pull, command quoting, cancellation, and rollback without an external Git or SSH executable.