cli: Enable redacting patch revisions

Use `rad patch redact <revision-id>` to redact (delete) a patch
revision.
This commit is contained in:
Slack Coder 2023-05-31 14:32:52 -05:00 committed by Alexis Sellier
parent 13bea902b2
commit 65763a2ad2
No known key found for this signature in database
3 changed files with 103 additions and 2 deletions

View File

@ -14,6 +14,8 @@ mod edit;
mod list;
#[path = "patch/ready.rs"]
mod ready;
#[path = "patch/redact.rs"]
mod redact;
#[path = "patch/show.rs"]
mod show;
#[path = "patch/update.rs"]
@ -49,6 +51,7 @@ Usage
rad patch update <patch-id> [<option>...]
rad patch checkout <patch-id> [<option>...]
rad patch delete <patch-id> [<option>...]
rad patch redact <revision-id> [<option>...]
rad patch ready <patch-id> [--undo] [<option>...]
rad patch edit <patch-id> [<option>...]
@ -99,6 +102,7 @@ pub enum OperationName {
#[default]
List,
Edit,
Redact,
}
pub struct Filter(fn(&patch::State) -> bool);
@ -158,6 +162,9 @@ pub enum Operation {
patch_id: Rev,
message: Message,
},
Redact {
revision_id: Rev,
},
}
#[derive(Debug)]
@ -179,6 +186,7 @@ impl Args for Options {
let mut fetch = false;
let mut announce = false;
let mut patch_id = None;
let mut revision_id = None;
let mut message = Message::default();
let mut push = true;
let mut filter = Filter::default();
@ -274,8 +282,13 @@ impl Args for Options {
"a" | "archive" => op = Some(OperationName::Archive),
"y" | "ready" => op = Some(OperationName::Ready),
"e" | "edit" => op = Some(OperationName::Edit),
"r" | "redact" => op = Some(OperationName::Redact),
unknown => anyhow::bail!("unknown operation '{}'", unknown),
},
Value(val) if op == Some(OperationName::Redact) => {
let val = string(&val);
revision_id = Some(Rev::from(val));
}
Value(val)
if patch_id.is_none()
&& [
@ -329,6 +342,9 @@ impl Args for Options {
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
message,
},
OperationName::Redact => Operation::Redact {
revision_id: revision_id.ok_or_else(|| anyhow!("a revision must be provided"))?,
},
};
Ok((
@ -408,7 +424,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
ready::run(&patch_id, undo, &profile, &repository)?;
}
Operation::Delete { patch_id } => {
let patch_id = patch_id.resolve(&repository.backend)?;
let patch_id = patch_id.resolve::<PatchId>(&repository.backend)?;
delete::run(&patch_id, &profile, &repository)?;
}
Operation::Checkout { patch_id } => {
@ -419,6 +435,9 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let patch_id = patch_id.resolve(&repository.backend)?;
edit::run(&patch_id, message, &profile, &repository)?;
}
Operation::Redact { revision_id } => {
redact::run(&revision_id, &profile, &repository)?;
}
}
Ok(())
}

View File

@ -0,0 +1,28 @@
use radicle::cob::patch;
use radicle::prelude::*;
use radicle::storage::git::Repository;
use crate::git;
use super::*;
pub fn run(
revision_id: &git::Rev,
profile: &Profile,
repository: &Repository,
) -> anyhow::Result<()> {
let signer = &term::signer(profile)?;
let mut patches = patch::Patches::open(repository)?;
let revision_id = revision_id.resolve::<patch::RevisionId>(&repository.backend)?;
let Some((patch_id, _, _)) = patches.find_by_revision(&revision_id)? else {
anyhow::bail!("patch revision `{revision_id}` not found");
};
let Ok(mut patch) = patches.get_mut(&patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};
patch.redact(revision_id, signer)?;
Ok(())
}

View File

@ -51,6 +51,9 @@ pub type RevisionIx = usize;
/// Error applying an operation onto a state.
#[derive(Debug, Error)]
pub enum Error {
/// Error trying to delete the protected root revision.
#[error("refusing to delete root revision: {0}")]
RootRevision(RevisionId),
/// Causal dependency missing.
///
/// This error indicates that the operations are not being applied
@ -862,6 +865,11 @@ impl store::Transaction<Patch> {
})
}
/// Redact the revision.
pub fn redact(&mut self, revision: RevisionId) -> Result<(), store::Error> {
self.push(Action::Redact { revision })
}
/// Start a patch revision discussion.
pub fn thread<S: ToString>(
&mut self,
@ -1011,11 +1019,23 @@ impl<'a, 'g> PatchMut<'a, 'g> {
description: String,
signer: &G,
) -> Result<EntryId, Error> {
self.transaction("Edit Revision", signer, |tx| {
self.transaction("Edit revision", signer, |tx| {
tx.edit_revision(revision, description)
})
}
/// Redact a revision.
pub fn redact<G: Signer>(
&mut self,
revision: RevisionId,
signer: &G,
) -> Result<EntryId, Error> {
if revision == RevisionId::from(self.id) {
return Err(Error::RootRevision(revision));
}
self.transaction("Redact revision", signer, |tx| tx.redact(revision))
}
/// Create a thread on a patch revision.
pub fn thread<G: Signer, S: ToString>(
&mut self,
@ -1994,4 +2014,38 @@ mod test {
assert_eq!(revision.oid, update.oid);
assert_eq!(revision.description(), "I've made changes.");
}
#[test]
fn test_patch_redact() {
let tmp = tempfile::tempdir().unwrap();
let ctx = test::setup::Context::new(&tmp);
let signer = &ctx.signer;
let pr = ctx.branch_with(test::setup::initial_blobs());
let mut patches = Patches::open(&ctx.project).unwrap();
let mut patch = patches
.create(
"My first patch",
"Blah blah blah.",
MergeTarget::Delegates,
pr.base,
pr.oid,
&[],
signer,
)
.unwrap();
let patch_id = patch.id;
let update = ctx.branch_with(test::setup::update_blobs());
let revision_id = patch
.update("I've made changes.", pr.base, update.oid, signer)
.unwrap();
assert_eq!(patch.revisions().count(), 2);
patch.redact(revision_id, signer).unwrap();
assert_eq!(patch.latest().0, &RevisionId::from(patch_id));
assert_eq!(patch.revisions().count(), 1);
// The patch's root must always exist.
assert!(patch.redact(*patch.latest().0, signer).is_err());
}
}