2.5 KiB
Tree mutations
TreeMutator owns the pass rm, mv, and cp filesystem semantics in
crates/storage. Callers provide typed command requests, a confirmation
decision, secret-key access when selective reencryption is needed, and a
TreeCommitter; they never inspect or mutate the password-store tree.
Sources use the repository resolver. A trailing slash explicitly selects a
directory, while a logical path occupied by both name.gpg and name/ is
otherwise rejected as ambiguous. An existing destination directory is a
container, so the source basename is appended. A destination ending in a slash
must already be a directory. Same-object transfers, moves into their own
subtree, entry/directory type collisions, symlinks, and subtrees containing
unknown auxiliary files are rejected before any write.
Entry overwrites require either --force or an affirmative confirmation.
Directory removal requires the recursive flag. Whole-directory transfers copy
their .gpg-id and optional .gpg-id.sig boundaries; an existing destination
policy is rejected instead of partly merging policy trees. Entries whose
effective destination recipients differ are decrypted and reencrypted with the
embedded OpenPGP backend, while matching ciphertext is preserved byte for byte.
Every destination entry and policy is atomically and durably written before a
move removes its source. The operation retains source and overwritten
destination bytes until the commit callback succeeds. A filesystem or commit
failure restores both sides and removes directories created by the failed
transaction. Successful moves and removals then prune empty source directories.
The commit callback must report failure only when it has not created a commit;
it receives the compatible intent Remove ... from store., Rename ... to ...., or Copy ... to .....
MutationOutcome also returns a typed post-operation selection for transfers,
identifying the actual destination entry or directory after container
semantics have been applied. Presentation layers use this result to refresh
selection without reimplementing path or destination rules. Removals omit a
target so a refreshed tree can retain the nearest surviving row.
Compatibility tests materialize the shared upstream-format fixtures and cover entry and subtree mutations, recursive requirements, cancellation, forced overwrite, directory destinations, ambiguity, nested signed recipient boundaries, selective reencryption, source preservation, source cleanup, and exact-tree rollback after a simulated commit failure.