refactor(cli): batched purge in cmd_purge and cmd_trash_empty (Plan B Phase 6)
Renames purge_item to purge_item_filesystem — body becomes filesystem-only (remove item.enc, remove attachments/<id>/, manifest.remove). Returns the relative paths it removed. cmd_purge and cmd_trash_empty accumulate the paths and fire ONE git rm + ONE git add + ONE git commit per invocation. A 50-item trash empty now produces 3 git subprocesses regardless of N (was N+2). New regression test trash_empty_batches_into_one_commit asserts the one-commit invariant via git rev-list --count.
This commit is contained in:
@@ -39,29 +39,29 @@ pub fn cmd_restore(query: String) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Inner purge: assumes vault is already unlocked and manifest is loaded.
|
||||
/// Caller is responsible for saving the manifest and committing afterwards.
|
||||
pub(super) fn purge_item(
|
||||
/// Filesystem-only purge: removes the item.enc, attachments/<id>/, and updates
|
||||
/// the manifest in memory. Returns the relative paths the caller must stage
|
||||
/// via `git rm` after the loop. Does NOT invoke any git commands — the caller
|
||||
/// batches them.
|
||||
pub(super) fn purge_item_filesystem(
|
||||
vault: &crate::session::UnlockedVault,
|
||||
manifest: &mut relicario_core::Manifest,
|
||||
id: &relicario_core::ItemId,
|
||||
title: &str,
|
||||
) -> Result<()> {
|
||||
) -> Result<Vec<String>> {
|
||||
use std::fs;
|
||||
|
||||
let item_rel = format!("items/{}.enc", id.as_str());
|
||||
let att_rel = format!("attachments/{}", id.as_str());
|
||||
|
||||
let item_path = vault.item_path(id);
|
||||
if item_path.exists() { fs::remove_file(&item_path)?; }
|
||||
let att_dir = vault.root().join("attachments").join(id.as_str());
|
||||
if att_dir.exists() { fs::remove_dir_all(&att_dir)?; }
|
||||
manifest.remove(id);
|
||||
|
||||
let _ = crate::helpers::git_command(vault.root(), &["rm", "-rf", "--ignore-unmatch",
|
||||
&format!("items/{}.enc", id.as_str()),
|
||||
&format!("attachments/{}", id.as_str()),
|
||||
]).status()?;
|
||||
// Note: caller adds+commits manifest.enc after processing all purges.
|
||||
eprintln!("Purged: {title}");
|
||||
Ok(())
|
||||
Ok(vec![item_rel, att_rel])
|
||||
}
|
||||
|
||||
pub fn cmd_purge(query: String) -> Result<()> {
|
||||
@@ -72,11 +72,19 @@ pub fn cmd_purge(query: String) -> Result<()> {
|
||||
let title = entry.title.clone();
|
||||
let _ = entry;
|
||||
|
||||
purge_item(&vault, &mut manifest, &id, &title)?;
|
||||
let paths = purge_item_filesystem(&vault, &mut manifest, &id, &title)?;
|
||||
vault.after_manifest_change(&manifest)?;
|
||||
|
||||
let purge_ctx = format!("purge \"{}\" ({})", title, id.as_str());
|
||||
crate::helpers::git_run(vault.root(), &["add", "manifest.enc"], &format!("{purge_ctx}: git add manifest.enc"))?;
|
||||
let mut rm_args: Vec<&str> = vec!["rm", "-rf", "--ignore-unmatch"];
|
||||
let path_refs: Vec<&str> = paths.iter().map(String::as_str).collect();
|
||||
rm_args.extend(path_refs.iter().copied());
|
||||
crate::helpers::git_run(vault.root(), &rm_args, &format!("{purge_ctx}: git rm"))?;
|
||||
crate::helpers::git_run(
|
||||
vault.root(),
|
||||
&["add", "manifest.enc"],
|
||||
&format!("{purge_ctx}: git add manifest.enc"),
|
||||
)?;
|
||||
crate::helpers::git_run(
|
||||
vault.root(),
|
||||
&["commit", "-m", &format!("purge: {} ({})", title, id.as_str())],
|
||||
@@ -113,13 +121,19 @@ pub fn cmd_trash_empty() -> Result<()> {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let mut purged_titles = Vec::new();
|
||||
let mut all_paths: Vec<String> = Vec::new();
|
||||
let purged_count = purgeable.len();
|
||||
for (id, title) in purgeable {
|
||||
purge_item(&vault, &mut manifest, &id, &title)?;
|
||||
purged_titles.push(title);
|
||||
let mut paths = purge_item_filesystem(&vault, &mut manifest, &id, &title)?;
|
||||
all_paths.append(&mut paths);
|
||||
}
|
||||
|
||||
vault.after_manifest_change(&manifest)?;
|
||||
|
||||
let mut rm_args: Vec<&str> = vec!["rm", "-rf", "--ignore-unmatch"];
|
||||
let path_refs: Vec<&str> = all_paths.iter().map(String::as_str).collect();
|
||||
rm_args.extend(path_refs.iter().copied());
|
||||
crate::helpers::git_run(vault.root(), &rm_args, "trash empty: git rm")?;
|
||||
crate::helpers::git_run(
|
||||
vault.root(),
|
||||
&["add", "manifest.enc"],
|
||||
@@ -127,10 +141,10 @@ pub fn cmd_trash_empty() -> Result<()> {
|
||||
)?;
|
||||
crate::helpers::git_run(
|
||||
vault.root(),
|
||||
&["commit", "-m", &format!("trash empty: purged {} item(s)", purged_titles.len())],
|
||||
&["commit", "-m", &format!("trash empty: purged {} item(s)", purged_count)],
|
||||
"trash empty: git commit",
|
||||
)?;
|
||||
|
||||
eprintln!("Emptied trash: {} item(s)", purged_titles.len());
|
||||
eprintln!("Emptied trash: {} item(s)", purged_count);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user