Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
cli: Enable redacting patch revisions
Slack Coder committed 2 years ago
commit 65763a2ad25de626a20166d4fcf44d24e0ccb789
parent 13bea902b231aec910cd49453d5b8d6582971294
3 files changed +103 -2
modified radicle-cli/src/commands/patch.rs
@@ -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(())
}
added radicle-cli/src/commands/patch/redact.rs
@@ -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(())
+
}
modified radicle/src/cob/patch.rs
@@ -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());
+
    }
}