cli: Create `rad patch ready` command

Allows for converting patches to and from draft mode.
This commit is contained in:
Alexis Sellier 2023-04-26 16:53:24 +02:00
parent f111681dd6
commit 12a91bd0ae
No known key found for this signature in database
5 changed files with 162 additions and 0 deletions

View File

@ -0,0 +1,69 @@
Let's say we have some changes in a branch:
```
$ git checkout -b cloudhead/draft
$ git commit -a -m "Nothing to see here.." -q --allow-empty
```
To open a patch in draft mode, we use the `--draft` option:
```
$ rad patch open --draft -m "Nothing yet" --quiet
3bdbfc4f85b942293ba7adb8e47bf3202a602e8b
```
We can confirm it's a draft by running `show`:
```
$ rad patch show 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Title Nothing yet │
│ Patch 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │
│ Commits ahead 1, behind 0 │
│ Status draft │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ] │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
Once the patch is ready for review, we can use the `ready` command:
```
$ rad patch ready 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b
```
```
$ rad patch show 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Title Nothing yet │
│ Patch 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │
│ Commits ahead 1, behind 0 │
│ Status open │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ] │
╰─────────────────────────────────────────────────────────────────────────────────────────╯
```
If for whatever reason, it needed to go back into draft mode, we could use
the `--undo` flag:
```
$ rad patch ready --undo 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b
$ rad patch show 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b
╭─────────────────────────────────────────────────────────────────────────────────────────╮
│ Title Nothing yet │
│ Patch 3bdbfc4f85b942293ba7adb8e47bf3202a602e8b │
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
│ Head 2a465832b5a76abe25be44a3a5d224bbd7741ba7 │
│ Branches cloudhead/draft │
│ Commits ahead 1, behind 0 │
│ Status draft │
├─────────────────────────────────────────────────────────────────────────────────────────┤
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ] │
╰─────────────────────────────────────────────────────────────────────────────────────────╯

View File

@ -10,6 +10,8 @@ mod create;
mod delete;
#[path = "patch/list.rs"]
mod list;
#[path = "patch/ready.rs"]
mod ready;
#[path = "patch/show.rs"]
mod show;
#[path = "patch/update.rs"]
@ -45,6 +47,7 @@ Usage
rad patch update <patch-id> [<option>...]
rad patch checkout <patch-id> [<option>...]
rad patch delete <patch-id> [<option>...]
rad patch ready <patch-id> [--undo] [<option>...]
Show options
@ -66,6 +69,10 @@ List options
--merged Show only merged patches
--open Show only open patches (default)
Ready options
--undo Convert a patch back to a draft
Other options
--help Print help
@ -80,6 +87,7 @@ pub enum OperationName {
Archive,
Delete,
Checkout,
Ready,
#[default]
List,
}
@ -103,6 +111,10 @@ pub enum Operation {
Archive {
patch_id: Rev,
},
Ready {
patch_id: Rev,
undo: bool,
},
Delete {
patch_id: Rev,
},
@ -138,6 +150,7 @@ impl Args for Options {
let mut filter = Some(patch::State::Open);
let mut diff = false;
let mut draft = false;
let mut undo = false;
let mut quiet = false;
while let Some(arg) = parser.next()? {
@ -187,6 +200,11 @@ impl Args for Options {
diff = true;
}
// Ready options.
Long("undo") if op == Some(OperationName::Ready) => {
undo = true;
}
// List options.
Long("all") => {
filter = None;
@ -217,6 +235,7 @@ impl Args for Options {
"d" | "delete" => op = Some(OperationName::Delete),
"c" | "checkout" => op = Some(OperationName::Checkout),
"a" | "archive" => op = Some(OperationName::Archive),
"y" | "ready" => op = Some(OperationName::Ready),
unknown => anyhow::bail!("unknown operation '{}'", unknown),
},
Value(val)
@ -226,6 +245,7 @@ impl Args for Options {
Some(OperationName::Update),
Some(OperationName::Delete),
Some(OperationName::Archive),
Some(OperationName::Ready),
Some(OperationName::Checkout),
]
.contains(&op) =>
@ -262,6 +282,10 @@ impl Args for Options {
OperationName::Checkout => Operation::Checkout {
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
},
OperationName::Ready => Operation::Ready {
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
undo,
},
};
Ok((
@ -336,6 +360,10 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
let patch_id = patch_id.resolve::<PatchId>(&repository.backend)?;
archive::run(&repository, &profile, &patch_id)?;
}
Operation::Ready { ref patch_id, undo } => {
let patch_id = patch_id.resolve::<PatchId>(&repository.backend)?;
ready::run(&repository, &profile, &patch_id, undo)?;
}
Operation::Delete { patch_id } => {
let patch_id = patch_id.resolve(&repository.backend)?;
delete::run(&repository, &profile, &patch_id)?;

View File

@ -0,0 +1,25 @@
use super::*;
use radicle::cob::patch;
use radicle::prelude::*;
use radicle::storage::git::Repository;
pub fn run(
repository: &Repository,
profile: &Profile,
patch_id: &PatchId,
undo: bool,
) -> anyhow::Result<()> {
let signer = term::signer(profile)?;
let mut patches = patch::Patches::open(repository)?;
let Ok(mut patch) = patches.get_mut(patch_id) else {
anyhow::bail!("Patch `{patch_id}` not found");
};
if undo {
patch.unready(&signer)?;
} else {
patch.ready(&signer)?;
}
Ok(())
}

View File

@ -271,6 +271,26 @@ fn rad_patch() {
test("examples/rad-patch.md", working.path(), Some(home), []).unwrap();
}
#[test]
fn rad_patch_draft() {
let mut environment = Environment::new();
let profile = environment.profile("alice");
let working = tempfile::tempdir().unwrap();
let home = &profile.home;
// Setup a test repository.
fixtures::repository(working.path());
test("examples/rad-init.md", working.path(), Some(home), []).unwrap();
test(
"examples/rad-patch-draft.md",
working.path(),
Some(home),
[],
)
.unwrap();
}
#[test]
fn rad_rm() {
let mut environment = Environment::new();

View File

@ -980,6 +980,26 @@ impl<'a, 'g> PatchMut<'a, 'g> {
self.lifecycle(State::Archived, signer)
}
/// Mark a patch as ready to be reviewed. Returns `false` if the patch was not a draft.
pub fn ready<G: Signer>(&mut self, signer: &G) -> Result<bool, Error> {
if self.state() != State::Draft {
return Ok(false);
}
self.lifecycle(State::Open, signer)?;
Ok(true)
}
/// Mark an open patch as a draft. Returns `false` if the patch was not open.
pub fn unready<G: Signer>(&mut self, signer: &G) -> Result<bool, Error> {
if self.state() != State::Open {
return Ok(false);
}
self.lifecycle(State::Draft, signer)?;
Ok(true)
}
/// Tag a patch.
pub fn tag<G: Signer>(
&mut self,