cli: Enable redacting patch revisions
Use `rad patch redact <revision-id>` to redact (delete) a patch revision.
This commit is contained in:
parent
13bea902b2
commit
65763a2ad2
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue