Implement TUI search and mutation workflows
This commit is contained in:
@@ -275,6 +275,36 @@ pub enum AutomaticPolicyCommitter {
|
||||
None(crate::recipient::NoGitCommitter),
|
||||
}
|
||||
|
||||
/// Storage-owned selection of pass-compatible automatic Git commits for a
|
||||
/// remove, move, or copy transaction.
|
||||
pub enum AutomaticTreeCommitter {
|
||||
Git(Box<GitRepository>),
|
||||
None(crate::mutation::NoGitTreeCommitter),
|
||||
}
|
||||
|
||||
impl AutomaticTreeCommitter {
|
||||
pub fn for_source(
|
||||
repository: &Repository,
|
||||
source: &str,
|
||||
identity: GitIdentity,
|
||||
) -> Result<Self, GitError> {
|
||||
match GitRepository::open_innermost(repository, Path::new(source), identity) {
|
||||
Ok(git) => Ok(Self::Git(Box::new(git))),
|
||||
Err(GitError::NotRepository) => Ok(Self::None(crate::mutation::NoGitTreeCommitter)),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TreeCommitter for AutomaticTreeCommitter {
|
||||
fn commit(&mut self, change: &TreeCommit) -> Result<(), TreeCommitError> {
|
||||
match self {
|
||||
Self::Git(git) => TreeCommitter::commit(git.as_mut(), change),
|
||||
Self::None(committer) => committer.commit(change),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AutomaticPolicyCommitter {
|
||||
pub fn for_directory(
|
||||
repository: &Repository,
|
||||
|
||||
@@ -84,6 +84,7 @@ impl TreeCommitter for NoGitTreeCommitter {
|
||||
pub struct MutationOutcome {
|
||||
action: MutationAction,
|
||||
entries: Vec<EntryPath>,
|
||||
selection: Option<MutationSelection>,
|
||||
}
|
||||
|
||||
impl MutationOutcome {
|
||||
@@ -93,6 +94,35 @@ impl MutationOutcome {
|
||||
pub fn entries(&self) -> &[EntryPath] {
|
||||
&self.entries
|
||||
}
|
||||
pub fn selection(&self) -> Option<&MutationSelection> {
|
||||
self.selection.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
pub enum MutationSelection {
|
||||
Entry(EntryPath),
|
||||
Directory(DirectoryPath),
|
||||
}
|
||||
|
||||
impl MutationSelection {
|
||||
pub fn path(&self) -> &std::path::Path {
|
||||
match self {
|
||||
Self::Entry(path) => path.as_path(),
|
||||
Self::Directory(path) => path.as_path(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_directory(&self) -> bool {
|
||||
matches!(self, Self::Directory(_))
|
||||
}
|
||||
|
||||
pub fn display_path(&self) -> String {
|
||||
match self {
|
||||
Self::Entry(path) => path.to_string(),
|
||||
Self::Directory(path) => path.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TreeMutator<'a> {
|
||||
@@ -172,6 +202,7 @@ impl<'a> TreeMutator<'a> {
|
||||
Ok(MutationOutcome {
|
||||
action: MutationAction::Remove,
|
||||
entries: entries.into_iter().map(|entry| entry.source).collect(),
|
||||
selection: None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -507,9 +538,14 @@ impl<'a> TreeMutator<'a> {
|
||||
if moving {
|
||||
self.repository.cleanup_empty_directories(&source_root)?;
|
||||
}
|
||||
let selection = destination_entry.map_or_else(
|
||||
|| MutationSelection::Directory(destination_root),
|
||||
MutationSelection::Entry,
|
||||
);
|
||||
Ok(MutationOutcome {
|
||||
action,
|
||||
entries: entries.into_iter().map(|entry| entry.destination).collect(),
|
||||
selection: Some(selection),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -294,6 +294,10 @@ impl GrepResults {
|
||||
self.entries.is_empty()
|
||||
}
|
||||
|
||||
pub fn includes_line_numbers(&self) -> bool {
|
||||
self.line_numbers
|
||||
}
|
||||
|
||||
/// Render matched bytes without requiring the CLI to reconstruct entry semantics.
|
||||
pub fn render_plain(&self) -> SecretBytes {
|
||||
let mut rendered = Vec::new();
|
||||
|
||||
@@ -8,9 +8,10 @@ use ironstorage::{
|
||||
command::{CopyRequest, MoveRequest, RemoveRequest},
|
||||
crypto::{KeyInfo, KeyStore, SecretProvider, SecretProviderError},
|
||||
mutation::{
|
||||
MutationAction, MutationError, TreeCommit, TreeCommitError, TreeCommitter, TreeMutator,
|
||||
MutationAction, MutationError, MutationSelection, TreeCommit, TreeCommitError,
|
||||
TreeCommitter, TreeMutator,
|
||||
},
|
||||
repository::{EntryPath, Repository, SecretBytes},
|
||||
repository::{DirectoryPath, EntryPath, Repository, SecretBytes},
|
||||
write::OverwriteDecision,
|
||||
};
|
||||
use support::compatibility::{FixtureSet, TestResult};
|
||||
@@ -124,7 +125,7 @@ fn copy_preserves_source_and_reencrypts_for_destination_policy() -> TestResult {
|
||||
let mut committer = Committer::default();
|
||||
let mutator = TreeMutator::new(&repository, &keys);
|
||||
let source = repository.read_entry(&EntryPath::parse("email/personal")?)?;
|
||||
mutator.copy(
|
||||
let outcome = mutator.copy(
|
||||
&CopyRequest {
|
||||
source: "email/personal".into(),
|
||||
destination: "team/personal".into(),
|
||||
@@ -135,6 +136,12 @@ fn copy_preserves_source_and_reencrypts_for_destination_policy() -> TestResult {
|
||||
&mut provider,
|
||||
&mut committer,
|
||||
)?;
|
||||
assert_eq!(
|
||||
outcome.selection(),
|
||||
Some(&MutationSelection::Entry(EntryPath::parse(
|
||||
"team/personal"
|
||||
)?))
|
||||
);
|
||||
assert_eq!(
|
||||
repository.read_entry(&EntryPath::parse("email/personal")?)?,
|
||||
source
|
||||
@@ -168,7 +175,7 @@ fn existing_and_trailing_slash_destinations_have_directory_semantics() -> TestRe
|
||||
let mut provider = Secrets::all(&fixture);
|
||||
let mutator = TreeMutator::new(&repository, &keys);
|
||||
|
||||
mutator.copy(
|
||||
let copied = mutator.copy(
|
||||
&CopyRequest {
|
||||
source: "email/personal".into(),
|
||||
destination: "archive/".into(),
|
||||
@@ -179,9 +186,15 @@ fn existing_and_trailing_slash_destinations_have_directory_semantics() -> TestRe
|
||||
&mut provider,
|
||||
&mut Committer::default(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
copied.selection(),
|
||||
Some(&MutationSelection::Entry(EntryPath::parse(
|
||||
"archive/personal"
|
||||
)?))
|
||||
);
|
||||
assert!(store.path().join("archive/personal.gpg").is_file());
|
||||
|
||||
mutator.copy(
|
||||
let copied_directory = mutator.copy(
|
||||
&CopyRequest {
|
||||
source: "team/".into(),
|
||||
destination: "archive/".into(),
|
||||
@@ -192,6 +205,12 @@ fn existing_and_trailing_slash_destinations_have_directory_semantics() -> TestRe
|
||||
&mut provider,
|
||||
&mut Committer::default(),
|
||||
)?;
|
||||
assert_eq!(
|
||||
copied_directory.selection(),
|
||||
Some(&MutationSelection::Directory(DirectoryPath::parse(
|
||||
"archive/team"
|
||||
)?))
|
||||
);
|
||||
assert!(store.path().join("archive/team/service.gpg").is_file());
|
||||
assert!(store.path().join("team/service.gpg").is_file());
|
||||
|
||||
|
||||
Reference in New Issue
Block a user