Complete the end-to-end CLI parity audit

This commit is contained in:
Hermes Agent committed 2026-08-10 01:51:47 +00:00
1 parent 4bd39b1ef2
commit 0a905e5e14
14 files changed
+1476 -95

No files matched your search

+64 -1
View File
@@ -24,7 +24,7 @@ use crate::{
crypto::{KeyHandle, KeyStore, SecretProvider},
mutation::{TreeCommit, TreeCommitError, TreeCommitter},
recipient::{PolicyCommit, PolicyCommitError, PolicyCommitter},
repository::Repository,
repository::{EncryptedEntry, Repository, SecretBytes},
write::{EntryCommit, EntryCommitError, EntryCommitter},
};
@@ -1587,6 +1587,41 @@ impl GitRepository {
Ok(output)
}
/// Render a helper-free working-tree diff. Password entries are decrypted
/// in storage before rendering, so ciphertext is never used as display
/// state and no Git textconv or GPG process is required.
pub fn render_diff(
&self,
paths: &[PathBuf],
keys: &KeyStore,
secrets: &mut impl SecretProvider,
) -> Result<SecretBytes, GitError> {
let mut rendered = Vec::new();
for entry in self.diff(paths)? {
let display = entry.path().to_string_lossy();
rendered.extend_from_slice(
format!("diff --ironstorage a/{display} b/{display}\n").as_bytes(),
);
match entry.old() {
Some(_) => rendered.extend_from_slice(format!("--- a/{display}\n").as_bytes()),
None => rendered.extend_from_slice(b"--- /dev/null\n"),
}
match entry.current() {
Some(_) => rendered.extend_from_slice(format!("+++ b/{display}\n").as_bytes()),
None => rendered.extend_from_slice(b"+++ /dev/null\n"),
}
if let Some(old) = entry.old() {
let plaintext = diff_contents(entry.path(), old, keys, secrets)?;
append_diff_lines(&mut rendered, b'-', plaintext.expose());
}
if let Some(current) = entry.current() {
let plaintext = diff_contents(entry.path(), current, keys, secrets)?;
append_diff_lines(&mut rendered, b'+', plaintext.expose());
}
}
Ok(SecretBytes::new(rendered))
}
fn head_tree_map(&self) -> Result<Option<BTreeMap<String, gix::hash::ObjectId>>, GitError> {
let Some(id) = self.repository.head_id().ok() else {
return Ok(None);
@@ -1639,6 +1674,34 @@ impl GitRepository {
}
}
fn diff_contents(
path: &Path,
contents: &[u8],
keys: &KeyStore,
secrets: &mut impl SecretProvider,
) -> Result<SecretBytes, GitError> {
if path.extension().is_some_and(|extension| extension == "gpg") {
keys.decrypt(&EncryptedEntry::new(contents.to_vec()), secrets)
.map_err(invalid)
} else {
Ok(SecretBytes::new(contents.to_vec()))
}
}
fn append_diff_lines(output: &mut Vec<u8>, prefix: u8, contents: &[u8]) {
if contents.is_empty() {
return;
}
for line in contents.split_inclusive(|byte| *byte == b'\n') {
output.push(prefix);
output.extend_from_slice(line);
if !line.ends_with(b"\n") {
output.push(b'\n');
output.extend_from_slice(b"\\ No newline at end of file\n");
}
}
}
impl EntryCommitter for GitRepository {
fn commit(&mut self, change: &EntryCommit) -> Result<(), EntryCommitError> {
self.stage_and_commit(&[change.path().encrypted_relative_path()], change.message())