bin: Layout basic queue logic for patch review

This commit is contained in:
Erik Kundt
2024-11-08 17:41:58 +01:00
parent f461bbbd48
commit d4e1c3a0b5
4 changed files with 119 additions and 26 deletions
-1
View File
@@ -80,7 +80,6 @@ pub fn all(profile: &Profile, repository: &Repository) -> Result<Vec<(PatchId, P
Ok(patches.flatten().collect())
}
#[allow(dead_code)]
pub fn find(profile: &Profile, repository: &Repository, id: &PatchId) -> Result<Option<Patch>> {
let cache = profile.patches(repository)?;
Ok(cache.get(id)?)
+73 -17
View File
@@ -9,16 +9,21 @@ use std::ffi::OsString;
use anyhow::anyhow;
use radicle::crypto::Signer;
use radicle::identity::RepoId;
use radicle::patch::Status;
use radicle::storage::WriteRepository;
use radicle_cli::git::Rev;
use radicle_cli::terminal;
use radicle_cli::terminal::args::{Args, Error, Help};
use radicle_cli::terminal::args::{string, Args, Error, Help};
use crate::cob::patch;
use crate::cob::patch::Filter;
use crate::commands::tui_patch::review::ReviewAction;
use crate::tui_patch::review::builder::{Brain, ReviewBuilder};
pub const HELP: Help = Help {
name: "patch",
description: "Terminal interfaces for patches",
@@ -56,14 +61,14 @@ pub struct Options {
}
pub enum Operation {
Review,
Select { opts: SelectOptions },
Review { opts: ReviewOptions },
}
#[derive(PartialEq, Eq)]
pub enum OperationName {
Review,
Select,
Review,
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
@@ -72,6 +77,12 @@ pub struct SelectOptions {
filter: patch::Filter,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ReviewOptions {
patch_id: Rev,
revision_id: Option<Rev>,
}
impl Args for Options {
fn from_args(args: Vec<OsString>) -> anyhow::Result<(Self, Vec<OsString>)> {
use lexopt::prelude::*;
@@ -80,6 +91,8 @@ impl Args for Options {
let mut op: Option<OperationName> = None;
let mut repo = None;
let mut select_opts = SelectOptions::default();
let mut patch_id = None;
let mut revision_id = None;
while let Some(arg) = parser.next()? {
match arg {
@@ -121,19 +134,27 @@ impl Args for Options {
.filter
.with_author(terminal::args::did(&parser.value()?)?);
}
Long("repo") => {
let val = parser.value()?;
let rid = terminal::args::rid(&val)?;
repo = Some(rid);
}
Long("revision") => {
let val = parser.value()?;
let rev_id = terminal::args::rev(&val)?;
revision_id = Some(rev_id);
}
Value(val) if op.is_none() => match val.to_string_lossy().as_ref() {
"select" => op = Some(OperationName::Select),
"review" => op = Some(OperationName::Review),
unknown => anyhow::bail!("unknown operation '{}'", unknown),
},
Value(val) if patch_id.is_none() => {
let val = string(&val);
patch_id = Some(Rev::from(val));
}
_ => return Err(anyhow!(arg.unexpected())),
}
}
@@ -143,7 +164,12 @@ impl Args for Options {
}
let op = match op.ok_or_else(|| anyhow!("an operation must be provided"))? {
OperationName::Review => Operation::Review,
OperationName::Review => Operation::Review {
opts: ReviewOptions {
patch_id: patch_id.ok_or_else(|| anyhow!("a patch must be provided"))?,
revision_id: revision_id,
},
},
OperationName::Select => Operation::Select { opts: select_opts },
};
Ok((Options { op, repo }, vec![]))
@@ -185,38 +211,68 @@ pub async fn run(options: Options, ctx: impl terminal::Context) -> anyhow::Resul
eprint!("{output}");
}
Operation::Review => {
let profile = ctx.profile()?;
let rid = options.repo.unwrap_or(rid);
let repository = profile.storage.repository(rid).unwrap();
Operation::Review { opts } => {
if let Err(err) = crate::log::enable() {
println!("{}", err);
}
log::info!("Starting patch review interface in project {}..", rid);
log::info!("Starting patch review interface in project {rid}..");
let mut queue = vec![0, 1, 2];
let profile = ctx.profile()?;
let signer = terminal::signer(&profile)?;
let rid = options.repo.unwrap_or(rid);
let repo = profile.storage.repository(rid).unwrap();
// Load patch
let patch_id = opts.patch_id.resolve(&repo.backend)?;
let patch = patch::find(&profile, &repo, &patch_id)?
.ok_or_else(|| anyhow!("Patch `{patch_id}` not found"))?;
// Load revision
let revision_id = opts
.revision_id
.map(|rev| rev.resolve::<radicle::git::Oid>(&repo.backend))
.transpose()?
.map(radicle::cob::patch::RevisionId::from);
let (_revision_id, revision) = match revision_id {
Some(id) => (
id,
patch
.revision(&id)
.ok_or_else(|| anyhow!("Patch revision `{id}` not found"))?,
),
None => patch.latest(),
};
let brain = if let Ok(b) = Brain::load(patch_id, signer.public_key(), repo.raw()) {
log::info!(
"Loaded existing review {} for patch {}",
b.head().id(),
&patch_id
);
b
} else {
let base = repo.raw().find_commit((*revision.base()).into())?;
Brain::new(patch_id, signer.public_key(), base, repo.raw())?
};
let queue = ReviewBuilder::new(patch_id, signer, &repo).queue(&brain, &revision)?;
while !queue.is_empty() {
let selection = review::Tui::new(&profile, &repository).run().await?;
let selection = review::Tui::new(&profile, &repo, &queue).run().await?;
log::info!("Received selection from TUI: {:?}", selection);
if let Some(selection) = selection.as_ref() {
match ReviewAction::try_from(selection.action)? {
ReviewAction::Accept => {
// brain accept
queue.pop();
}
ReviewAction::Ignore => {
// next hunk
queue.pop();
}
ReviewAction::Comment => {
radicle_cli::terminal::Editor::new()
.extension("diff")
.edit(String::new())?;
queue.pop();
}
}
} else {
+6 -2
View File
@@ -1,5 +1,5 @@
#[path = "review/builder.rs"]
mod builder;
pub mod builder;
use anyhow::Result;
@@ -18,6 +18,8 @@ use tui::ui::im::widget::{TextViewState, Window};
use tui::ui::im::{Borders, Context, Show};
use tui::{Channel, Exit};
use self::builder::ReviewQueue;
/// The actions that a user can carry out on a review item.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum ReviewAction {
@@ -62,13 +64,15 @@ pub struct Selection {
pub struct Tui<'a> {
pub _profile: &'a Profile,
pub _repository: &'a Repository,
pub queue: &'a ReviewQueue,
}
impl<'a> Tui<'a> {
pub fn new(profile: &'a Profile, repository: &'a Repository) -> Self {
pub fn new(profile: &'a Profile, repository: &'a Repository, queue: &'a ReviewQueue) -> Self {
Self {
_profile: profile,
_repository: repository,
queue,
}
}
+40 -6
View File
@@ -486,7 +486,7 @@ pub struct Brain<'a> {
impl<'a> Brain<'a> {
/// Create a new brain in the repository.
fn new(
pub fn new(
patch: PatchId,
remote: &NodeId,
base: git::raw::Commit,
@@ -515,12 +515,12 @@ impl<'a> Brain<'a> {
/// Return the content identifier of this brain. This represents the state of the
/// accepted hunks, ie. the git tree.
fn cid(&self) -> Oid {
pub fn cid(&self) -> Oid {
self.accepted.id().into()
}
/// Load an existing brain from the repository.
fn load(
pub fn load(
patch: PatchId,
remote: &NodeId,
repo: &'a git::raw::Repository,
@@ -539,7 +539,7 @@ impl<'a> Brain<'a> {
}
/// Accept changes to the brain.
fn accept(
pub fn accept(
&mut self,
diff: git::raw::Diff,
repo: &'a git::raw::Repository,
@@ -563,9 +563,17 @@ impl<'a> Brain<'a> {
}
/// Get the brain's refname given the patch and remote.
fn refname(patch: &PatchId, remote: &NodeId) -> git::Namespaced<'a> {
pub fn refname(patch: &PatchId, remote: &NodeId) -> git::Namespaced<'a> {
git::refs::storage::draft::review(remote, patch)
}
pub fn head(&self) -> &git::raw::Commit<'a> {
&self.head
}
pub fn accepted(&self) -> &git::raw::Tree<'a> {
&self.accepted
}
}
/// Builds a patch review interactively, across multiple files.
@@ -606,6 +614,32 @@ impl<'a, G: Signer> ReviewBuilder<'a, G> {
self
}
/// Assemble the review for the given revision.
pub fn queue(&self, brain: &'a Brain<'a>, revision: &Revision) -> anyhow::Result<ReviewQueue> {
let repo = self.repo.raw();
let signer = &self.signer;
let patch_id = self.patch_id;
let base = repo.find_commit((*revision.base()).into())?;
let tree = {
let commit = repo.find_commit(revision.head().into())?;
commit.tree()?
};
let mut opts = git::raw::DiffOptions::new();
opts.patience(true).minimal(true).context_lines(3_u32);
let diff = self.diff(&brain.accepted(), &tree, repo, &mut opts)?;
let drafts = DraftStore::new(*signer.public_key(), self.repo).with(
signer.public_key(),
&cob::patch::TYPENAME,
&patch_id,
)?;
let mut patches = cob::patch::Cache::no_cache(&drafts)?;
let mut patch = patches.get_mut(&patch_id)?;
Ok(ReviewQueue::from(diff))
}
/// Run the review builder for the given revision.
pub fn run(self, revision: &Revision, opts: &mut git::raw::DiffOptions) -> anyhow::Result<()> {
let repo = self.repo.raw();
@@ -774,7 +808,7 @@ impl<'a, G: Signer> ReviewBuilder<'a, G> {
Ok(())
}
fn diff(
pub fn diff(
&self,
brain: &git::raw::Tree<'_>,
tree: &git::raw::Tree<'_>,