diff --git a/radicle-cli/src/commands/patch.rs b/radicle-cli/src/commands/patch.rs index b7bcc39a..c83e4b0c 100644 --- a/radicle-cli/src/commands/patch.rs +++ b/radicle-cli/src/commands/patch.rs @@ -40,7 +40,7 @@ Usage rad patch [...] rad patch list [--all|--merged|--open|--archived] [...] rad patch show [...] - rad patch open [...] + rad patch open [--draft] [...] rad patch archive [...] rad patch update [...] rad patch checkout [...] @@ -50,8 +50,9 @@ Show options -p, --patch Show the actual patch diff -Create/Update options +Open/Update options + --draft Open patch in draft mode --[no-]announce Announce patch to network (default: false) --[no-]push Push patch head to storage (default: true) -m, --message [] Provide a comment message to the patch or revision (default: prompt) @@ -86,6 +87,7 @@ pub enum OperationName { pub enum Operation { Open { message: Message, + draft: bool, }, Show { patch_id: Rev, @@ -132,6 +134,7 @@ impl Args for Options { let mut push = true; let mut filter = Some(patch::State::Open); let mut diff = false; + let mut draft = false; while let Some(arg) = parser.next()? { match arg { @@ -165,6 +168,11 @@ impl Args for Options { push = false; } + // Open options. + Long("draft") if op == Some(OperationName::Open) => { + draft = true; + } + // Show options. Long("patch") | Short('p') if op == Some(OperationName::Show) => { diff = true; @@ -221,7 +229,7 @@ impl Args for Options { } let op = match op.unwrap_or_default() { - OperationName::Open => Operation::Open { message }, + OperationName::Open => Operation::Open { message, draft }, OperationName::List => Operation::List { filter }, OperationName::Show => Operation::Show { patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?, @@ -266,8 +274,15 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { } match options.op { - Operation::Open { ref message } => { - create::run(&repository, &profile, &workdir, message.clone(), options)?; + Operation::Open { ref message, draft } => { + create::run( + &repository, + &profile, + &workdir, + message.clone(), + draft, + options, + )?; } Operation::List { filter } => { list::run(&repository, &profile, filter)?; diff --git a/radicle-cli/src/commands/patch/create.rs b/radicle-cli/src/commands/patch/create.rs index c38587fe..8cfe0b3f 100644 --- a/radicle-cli/src/commands/patch/create.rs +++ b/radicle-cli/src/commands/patch/create.rs @@ -84,6 +84,7 @@ pub fn run( profile: &Profile, workdir: &git::raw::Repository, message: term::patch::Message, + draft: bool, options: Options, ) -> anyhow::Result<()> { let mut patches = patch::Patches::open(storage)?; @@ -116,15 +117,27 @@ pub fn run( let head_oid = branch_oid(&head_branch)?; let base_oid = workdir.merge_base(*target_oid, *head_oid)?; let signer = term::signer(profile)?; - let patch = patches.create( - title, - &description, - patch::MergeTarget::default(), - base_oid, - head_oid, - &[], - &signer, - )?; + let patch = if draft { + patches.draft( + title, + &description, + patch::MergeTarget::default(), + base_oid, + head_oid, + &[], + &signer, + ) + } else { + patches.create( + title, + &description, + patch::MergeTarget::default(), + base_oid, + head_oid, + &[], + &signer, + ) + }?; term::success!("Patch {} created", term::format::highlight(patch.id)); term::blank(); diff --git a/radicle/src/cob/patch.rs b/radicle/src/cob/patch.rs index 7d27c2e6..8f06b2c1 100644 --- a/radicle/src/cob/patch.rs +++ b/radicle/src/cob/patch.rs @@ -1029,7 +1029,7 @@ impl<'a> Patches<'a> { Ok(Self { raw }) } - /// Create a patch. + /// Open a new patch. pub fn create<'g, G: Signer>( &'g mut self, title: impl ToString, @@ -1040,18 +1040,39 @@ impl<'a> Patches<'a> { tags: &[Tag], signer: &G, ) -> Result, Error> { - let (id, patch, clock) = - Transaction::initial("Create patch", &mut self.raw, signer, |tx| { - tx.revision(String::default(), base, oid)?; - tx.edit(title, description, target)?; - tx.tag(tags.to_owned(), [])?; + self._create( + title, + description, + target, + base, + oid, + tags, + State::default(), + signer, + ) + } - Ok(()) - })?; - // Just a sanity check that our clock is advancing as expected. - debug_assert_eq!(clock.get(), 1); - - Ok(PatchMut::new(id, patch, clock, self)) + /// Draft a patch. This patch will be created in a [`State::Draft`] state. + pub fn draft<'g, G: Signer>( + &'g mut self, + title: impl ToString, + description: impl ToString, + target: MergeTarget, + base: impl Into, + oid: impl Into, + tags: &[Tag], + signer: &G, + ) -> Result, Error> { + self._create( + title, + description, + target, + base, + oid, + tags, + State::Draft, + signer, + ) } /// Patches count by state. @@ -1132,6 +1153,35 @@ impl<'a> Patches<'a> { .proposed()? .filter(move |(_, p, _)| p.author().id() == who)) } + + /// Create a patch. This is an internal function used by `create` and `draft`. + fn _create<'g, G: Signer>( + &'g mut self, + title: impl ToString, + description: impl ToString, + target: MergeTarget, + base: impl Into, + oid: impl Into, + tags: &[Tag], + state: State, + signer: &G, + ) -> Result, Error> { + let (id, patch, clock) = + Transaction::initial("Create patch", &mut self.raw, signer, |tx| { + tx.revision(String::default(), base, oid)?; + tx.edit(title, description, target)?; + tx.tag(tags.to_owned(), [])?; + + if state != State::default() { + tx.lifecycle(state)?; + } + Ok(()) + })?; + // Just a sanity check that our clock is advancing as expected. + debug_assert_eq!(clock.get(), 1); + + Ok(PatchMut::new(id, patch, clock, self)) + } } #[cfg(test)]