cli: Display patches like issues, in `rad patch`
We also make sure not to bail if one of the patches fails to load.
This commit is contained in:
parent
b809bd3d73
commit
3b20d45d80
|
|
@ -41,19 +41,22 @@ It will now be listed as one of the project's open patches.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad patch
|
$ rad patch
|
||||||
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Define power requirements 191a14e R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │
|
│ ● ID Title Author Head + - Opened │
|
||||||
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
|
│ ● 191a14e Define power requirements z6MknSL…StBU8Vi (you) 3e674d1 +0 -0 4 months ago │
|
||||||
╰─────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
```
|
```
|
||||||
$ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 -p
|
$ rad patch show 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 -p
|
||||||
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Title Define power requirements │
|
│ Title Define power requirements │
|
||||||
│ Patch 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 │
|
│ Patch 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 │
|
||||||
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
|
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
|
||||||
│ Status open │
|
│ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
|
||||||
|
│ Branches flux-capacitor-power │
|
||||||
|
│ Commits ahead 1, behind 0 │
|
||||||
|
│ Status open │
|
||||||
│ │
|
│ │
|
||||||
│ See details. │
|
│ See details. │
|
||||||
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
|
@ -120,12 +123,20 @@ $ rad review 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 --accept --no-message --no
|
||||||
Showing the patch list now will reveal the favorable verdict:
|
Showing the patch list now will reveal the favorable verdict:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad patch
|
$ rad patch show 191a14e
|
||||||
╭──────────────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Define power requirements 191a14e R1 27857ec (flux-capacitor-power, patch/191a14e) ahead 2, behind 0 │
|
│ Title Define power requirements │
|
||||||
├──────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
│ Patch 191a14e520f2eeff7c0e3ee0a5523c5217eecb89 │
|
||||||
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
|
│ Author did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi │
|
||||||
│ ↑ updated to b8f7bfbbb3c6a207b349e9f45bf535c706805871 (27857ec) [ ... ]│
|
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
|
||||||
│ ✓ accepted by z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
|
│ Branches flux-capacitor-power, patch/191a14e │
|
||||||
╰──────────────────────────────────────────────────────────────────────────────────────────────────────╯
|
│ Commits ahead 2, behind 0 │
|
||||||
|
│ Status open │
|
||||||
|
│ │
|
||||||
|
│ See details. │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ ● opened by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
|
||||||
|
│ ↑ updated to b8f7bfbbb3c6a207b349e9f45bf535c706805871 (27857ec) [ ... ]│
|
||||||
|
│ ✓ accepted by z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -41,17 +41,20 @@ It will now be listed as one of the project's open patches.
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad patch
|
$ rad patch
|
||||||
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
╭──────────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Define power requirements a07ef77 R0 3e674d1 (flux-capacitor-power) ahead 1, behind 0 │
|
│ ● ID Title Author Head + - Opened │
|
||||||
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
├──────────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk (you) [ ... ]│
|
│ ● a07ef77 Define power requirements z6Mkt67…v4N1tRk (you) 3e674d1 +0 -0 4 months ago │
|
||||||
╰─────────────────────────────────────────────────────────────────────────────────────────╯
|
╰──────────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
$ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a
|
$ rad patch show a07ef7743a32a2e902672ea3526d1db6ee08108a
|
||||||
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Title Define power requirements │
|
│ Title Define power requirements │
|
||||||
│ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │
|
│ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │
|
||||||
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
|
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
|
||||||
│ Status open │
|
│ Head 3e674d1a1df90807e934f9ae5da2591dd6848a33 │
|
||||||
|
│ Branches flux-capacitor-power │
|
||||||
|
│ Commits ahead 1, behind 0 │
|
||||||
|
│ Status open │
|
||||||
│ │
|
│ │
|
||||||
│ See details. │
|
│ See details. │
|
||||||
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
|
|
||||||
|
|
@ -26,12 +26,19 @@ $ git branch -r
|
||||||
bob/flux-capacitor-power
|
bob/flux-capacitor-power
|
||||||
bob/master
|
bob/master
|
||||||
rad/master
|
rad/master
|
||||||
$ rad patch
|
$ rad patch show a07ef77
|
||||||
╭───────────────────────────────────────────────────────────────────────────────────╮
|
╭───────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Define power requirements a07ef77 R1 27857ec ahead 2, behind 0 │
|
│ Title Define power requirements │
|
||||||
|
│ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │
|
||||||
|
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
|
||||||
|
│ Head 27857ec9eb04c69cacab516e8bf4b5fd36090f66 │
|
||||||
|
│ Commits ahead 2, behind 0 │
|
||||||
|
│ Status open │
|
||||||
|
│ │
|
||||||
|
│ See details. │
|
||||||
├───────────────────────────────────────────────────────────────────────────────────┤
|
├───────────────────────────────────────────────────────────────────────────────────┤
|
||||||
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ]│
|
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ] │
|
||||||
│ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ]│
|
│ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ] │
|
||||||
╰───────────────────────────────────────────────────────────────────────────────────╯
|
╰───────────────────────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -76,15 +83,23 @@ Fast-forward
|
||||||
The patch is now merged and closed :).
|
The patch is now merged and closed :).
|
||||||
|
|
||||||
```
|
```
|
||||||
$ rad patch --merged
|
$ rad patch show a07ef77
|
||||||
╭───────────────────────────────────────────────────────────────────────────────────────────────╮
|
╭─────────────────────────────────────────────────────────────────────────────────────────╮
|
||||||
│ Define power requirements a07ef77 R2 f6484e0 (flux-capacitor-power, master) ahead 3, behind 0 │
|
│ Title Define power requirements │
|
||||||
├───────────────────────────────────────────────────────────────────────────────────────────────┤
|
│ Patch a07ef7743a32a2e902672ea3526d1db6ee08108a │
|
||||||
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ]│
|
│ Author did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk │
|
||||||
│ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ]│
|
│ Head f6484e0f43e48a8983b9b39bf9bd4cd889f1d520 │
|
||||||
│ ↑ updated to 0795d619232479e910f95bb9c873ee1ec305c43c (f6484e0) [ ... ]│
|
│ Branches flux-capacitor-power, master │
|
||||||
│ ✓ merged by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ]│
|
│ Commits ahead 3, behind 0 │
|
||||||
╰───────────────────────────────────────────────────────────────────────────────────────────────╯
|
│ Status merged │
|
||||||
|
│ │
|
||||||
|
│ See details. │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ ● opened by did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk [ ... ] │
|
||||||
|
│ ↑ updated to 11483929d8714a92992229f65433e06288f3b760 (27857ec) [ ... ] │
|
||||||
|
│ ↑ updated to 0795d619232479e910f95bb9c873ee1ec305c43c (f6484e0) [ ... ] │
|
||||||
|
│ ✓ merged by did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi (you) [ ... ] │
|
||||||
|
╰─────────────────────────────────────────────────────────────────────────────────────────╯
|
||||||
```
|
```
|
||||||
|
|
||||||
To publish our new state to the network, we simply push:
|
To publish our new state to the network, we simply push:
|
||||||
|
|
|
||||||
|
|
@ -270,7 +270,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
create::run(&repository, &profile, &workdir, message.clone(), options)?;
|
create::run(&repository, &profile, &workdir, message.clone(), options)?;
|
||||||
}
|
}
|
||||||
Operation::List { filter } => {
|
Operation::List { filter } => {
|
||||||
list::run(&repository, &profile, Some(workdir), filter)?;
|
list::run(&repository, &profile, filter)?;
|
||||||
}
|
}
|
||||||
Operation::Show { patch_id, diff } => {
|
Operation::Show { patch_id, diff } => {
|
||||||
let patch_id = patch_id.resolve(&repository.backend)?;
|
let patch_id = patch_id.resolve(&repository.backend)?;
|
||||||
|
|
|
||||||
|
|
@ -68,8 +68,23 @@ pub fn patch_merge_target_oid(target: MergeTarget, repository: &Repository) -> a
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the diff stats between two commits.
|
||||||
|
pub fn diff_stats(
|
||||||
|
repo: &git::raw::Repository,
|
||||||
|
old: &Oid,
|
||||||
|
new: &Oid,
|
||||||
|
) -> Result<git::raw::DiffStats, git::raw::Error> {
|
||||||
|
let old = repo.find_commit(*old)?;
|
||||||
|
let new = repo.find_commit(*new)?;
|
||||||
|
let old_tree = old.tree()?;
|
||||||
|
let new_tree = new.tree()?;
|
||||||
|
let diff = repo.diff_tree_to_tree(Some(&old_tree), Some(&new_tree), None)?;
|
||||||
|
|
||||||
|
diff.stats()
|
||||||
|
}
|
||||||
|
|
||||||
/// Create a human friendly message about git's sync status.
|
/// Create a human friendly message about git's sync status.
|
||||||
pub fn pretty_sync_status(
|
pub fn ahead_behind(
|
||||||
repo: &git::raw::Repository,
|
repo: &git::raw::Repository,
|
||||||
revision_oid: Oid,
|
revision_oid: Oid,
|
||||||
head_oid: Oid,
|
head_oid: Oid,
|
||||||
|
|
@ -89,35 +104,21 @@ pub fn pretty_sync_status(
|
||||||
.item(behind))
|
.item(behind))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Make a human friendly string for commit version information.
|
/// Get the branches that point to a commit.
|
||||||
///
|
pub fn branches(target: &Oid, repo: &git::raw::Repository) -> anyhow::Result<Vec<String>> {
|
||||||
/// For example '<oid> (branch1[, branch2])'.
|
|
||||||
pub fn pretty_commit_version(
|
|
||||||
revision_oid: &Oid,
|
|
||||||
repo: Option<&git::raw::Repository>,
|
|
||||||
) -> anyhow::Result<term::Line> {
|
|
||||||
let oid = term::format::secondary(term::format::oid(*revision_oid));
|
|
||||||
let mut line = term::Line::new(oid);
|
|
||||||
let mut branches: Vec<String> = vec![];
|
let mut branches: Vec<String> = vec![];
|
||||||
|
|
||||||
if let Some(repo) = repo {
|
for r in repo.references()?.flatten() {
|
||||||
for r in repo.references()?.flatten() {
|
if !r.is_branch() {
|
||||||
if !r.is_branch() {
|
continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
|
|
||||||
if oid == revision_oid {
|
|
||||||
branches.push(name.to_string());
|
|
||||||
};
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
};
|
if let (Some(oid), Some(name)) = (&r.target(), &r.shorthand()) {
|
||||||
if !branches.is_empty() {
|
if oid == target {
|
||||||
line.push(term::Label::space());
|
branches.push(name.to_string());
|
||||||
line.push(term::format::yellow(format!("({})", branches.join(", "))));
|
};
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Ok(branches)
|
||||||
Ok(line)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,13 @@
|
||||||
use anyhow::anyhow;
|
use anyhow::anyhow;
|
||||||
|
|
||||||
use radicle::cob::patch;
|
use radicle::cob::patch;
|
||||||
use radicle::cob::patch::{Patch, PatchId, Patches, Revision, Verdict};
|
use radicle::cob::patch::{Patch, PatchId, Patches, Verdict};
|
||||||
use radicle::git;
|
|
||||||
use radicle::prelude::*;
|
use radicle::prelude::*;
|
||||||
use radicle::profile::Profile;
|
use radicle::profile::Profile;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
use term::table::{Table, TableOptions};
|
||||||
use term::Element as _;
|
use term::Element as _;
|
||||||
|
|
||||||
use super::common;
|
use super::common;
|
||||||
|
|
@ -16,7 +16,6 @@ use super::common;
|
||||||
pub fn run(
|
pub fn run(
|
||||||
repository: &Repository,
|
repository: &Repository,
|
||||||
profile: &Profile,
|
profile: &Profile,
|
||||||
workdir: Option<git::raw::Repository>,
|
|
||||||
filter: Option<patch::State>,
|
filter: Option<patch::State>,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let me = *profile.id();
|
let me = *profile.id();
|
||||||
|
|
@ -48,62 +47,89 @@ pub fn run(
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut table = Table::<9, term::Line>::new(TableOptions {
|
||||||
|
spacing: 2,
|
||||||
|
border: Some(term::colors::FAINT),
|
||||||
|
..TableOptions::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
table.push([
|
||||||
|
term::format::dim(String::from("●")).into(),
|
||||||
|
term::format::bold(String::from("ID")).into(),
|
||||||
|
term::format::bold(String::from("Title")).into(),
|
||||||
|
term::format::bold(String::from("Author")).into(),
|
||||||
|
term::format::bold(String::new()).into(),
|
||||||
|
term::format::bold(String::from("Head")).into(),
|
||||||
|
term::format::bold(String::from("+")).into(),
|
||||||
|
term::format::bold(String::from("-")).into(),
|
||||||
|
term::format::bold(String::from("Opened")).into(),
|
||||||
|
]);
|
||||||
|
table.divider();
|
||||||
|
|
||||||
|
let mut errors = Vec::new();
|
||||||
for (id, patch) in &mut own {
|
for (id, patch) in &mut own {
|
||||||
widget(&me, id, patch, workdir.as_ref(), repository)?.print();
|
match row(&me, id, patch, repository) {
|
||||||
|
Ok(r) => table.push(r),
|
||||||
|
Err(e) => errors.push((patch.title(), id, e.to_string())),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (id, patch) in &mut other {
|
for (id, patch) in &mut other {
|
||||||
widget(profile.id(), id, patch, workdir.as_ref(), repository)?.print();
|
match row(&me, id, patch, repository) {
|
||||||
|
Ok(r) => table.push(r),
|
||||||
|
Err(e) => errors.push((patch.title(), id, e.to_string())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
table.print();
|
||||||
|
|
||||||
|
if !errors.is_empty() {
|
||||||
|
for (title, id, error) in errors {
|
||||||
|
term::error(format!(
|
||||||
|
"{} Patch {title:?} ({id}) failed to load: {error}",
|
||||||
|
term::format::negative("Error:")
|
||||||
|
));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn header(
|
/// Patch row.
|
||||||
patch_id: &PatchId,
|
pub fn row(
|
||||||
patch: &Patch,
|
|
||||||
workdir: Option<&git::raw::Repository>,
|
|
||||||
repository: &Repository,
|
|
||||||
revision: &Revision,
|
|
||||||
) -> anyhow::Result<term::Line> {
|
|
||||||
let target_head = common::patch_merge_target_oid(patch.target(), repository)?;
|
|
||||||
let header = term::Line::spaced([
|
|
||||||
term::format::bold(patch.title()).into(),
|
|
||||||
term::format::highlight(term::format::cob(patch_id)).into(),
|
|
||||||
term::format::dim(format!("R{}", patch.version())).into(),
|
|
||||||
])
|
|
||||||
.space()
|
|
||||||
.extend(common::pretty_commit_version(&revision.head(), workdir)?)
|
|
||||||
.space()
|
|
||||||
.extend(common::pretty_sync_status(
|
|
||||||
repository.raw(),
|
|
||||||
revision.head().into(),
|
|
||||||
target_head,
|
|
||||||
)?);
|
|
||||||
|
|
||||||
Ok(header)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Patch widget.
|
|
||||||
pub fn widget<'a>(
|
|
||||||
whoami: &PublicKey,
|
whoami: &PublicKey,
|
||||||
patch_id: &PatchId,
|
id: &PatchId,
|
||||||
patch: &Patch,
|
patch: &Patch,
|
||||||
workdir: Option<&git::raw::Repository>,
|
|
||||||
repository: &Repository,
|
repository: &Repository,
|
||||||
) -> anyhow::Result<term::VStack<'a>> {
|
) -> anyhow::Result<[term::Line; 9]> {
|
||||||
|
let state = patch.state();
|
||||||
let (_, revision) = patch
|
let (_, revision) = patch
|
||||||
.latest()
|
.latest()
|
||||||
.ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?;
|
.ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?;
|
||||||
let header = header(patch_id, patch, workdir, repository, revision)?;
|
let stats = common::diff_stats(repository.raw(), revision.base(), &revision.head())?;
|
||||||
let mut widget = term::VStack::default()
|
let author = patch.author().id;
|
||||||
.child(header)
|
|
||||||
.divider()
|
|
||||||
.border(Some(term::colors::FAINT));
|
|
||||||
|
|
||||||
for line in timeline(whoami, patch_id, patch, repository)? {
|
Ok([
|
||||||
widget.push(line);
|
match state {
|
||||||
}
|
patch::State::Open => term::format::positive("●").into(),
|
||||||
Ok(widget)
|
patch::State::Archived { .. } => term::format::yellow("●").into(),
|
||||||
|
patch::State::Draft => term::format::dim("●").into(),
|
||||||
|
patch::State::Merged { .. } => term::format::primary("✔").into(),
|
||||||
|
},
|
||||||
|
term::format::tertiary(term::format::cob(id)).into(),
|
||||||
|
term::format::default(patch.title().to_owned()).into(),
|
||||||
|
term::format::did(&author).dim().into(),
|
||||||
|
if author.as_key() == whoami {
|
||||||
|
term::format::primary("(you)".to_owned()).into()
|
||||||
|
} else {
|
||||||
|
term::format::default(String::new()).into()
|
||||||
|
},
|
||||||
|
term::format::secondary(term::format::oid(revision.head())).into(),
|
||||||
|
term::format::positive(format!("+{}", stats.insertions())).into(),
|
||||||
|
term::format::negative(format!("-{}", stats.deletions())).into(),
|
||||||
|
term::format::timestamp(&patch.timestamp())
|
||||||
|
.dim()
|
||||||
|
.italic()
|
||||||
|
.into(),
|
||||||
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn timeline(
|
pub fn timeline(
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ use radicle::git;
|
||||||
use radicle::storage::git::Repository;
|
use radicle::storage::git::Repository;
|
||||||
use radicle_term::{
|
use radicle_term::{
|
||||||
table::{Table, TableOptions},
|
table::{Table, TableOptions},
|
||||||
textarea, Element, Paint, VStack,
|
textarea, Element, VStack,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::terminal as term;
|
use crate::terminal as term;
|
||||||
|
|
@ -13,12 +13,7 @@ use crate::terminal as term;
|
||||||
use super::common::*;
|
use super::common::*;
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
fn show_patch_diff(
|
fn show_patch_diff(patch: &patch::Patch, storage: &Repository) -> anyhow::Result<()> {
|
||||||
patch: &patch::Patch,
|
|
||||||
storage: &Repository,
|
|
||||||
// TODO: Tell user which working copy branches point to the patch.
|
|
||||||
_workdir: &git::raw::Repository,
|
|
||||||
) -> anyhow::Result<()> {
|
|
||||||
let target_head = patch_merge_target_oid(patch.target(), storage)?;
|
let target_head = patch_merge_target_oid(patch.target(), storage)?;
|
||||||
let base_oid = storage.raw().merge_base(target_head, **patch.head())?;
|
let base_oid = storage.raw().merge_base(target_head, **patch.head())?;
|
||||||
let diff = format!("{}..{}", base_oid, patch.head());
|
let diff = format!("{}..{}", base_oid, patch.head());
|
||||||
|
|
@ -46,32 +41,53 @@ pub fn run(
|
||||||
let Some(patch) = patches.get(patch_id)? else {
|
let Some(patch) = patches.get(patch_id)? else {
|
||||||
anyhow::bail!("Patch `{patch_id}` not found");
|
anyhow::bail!("Patch `{patch_id}` not found");
|
||||||
};
|
};
|
||||||
|
let (_, revision) = patch
|
||||||
|
.latest()
|
||||||
|
.ok_or_else(|| anyhow!("patch is malformed: no revisions found"))?;
|
||||||
let state = patch.state();
|
let state = patch.state();
|
||||||
|
let branches = common::branches(&revision.head(), workdir)?;
|
||||||
|
let target_head = common::patch_merge_target_oid(patch.target(), stored)?;
|
||||||
|
let ahead_behind = common::ahead_behind(stored.raw(), revision.head().into(), target_head)?;
|
||||||
|
|
||||||
let mut attrs = Table::<2, Paint<String>>::new(TableOptions {
|
let mut attrs = Table::<2, term::Line>::new(TableOptions {
|
||||||
spacing: 2,
|
spacing: 2,
|
||||||
..TableOptions::default()
|
..TableOptions::default()
|
||||||
});
|
});
|
||||||
attrs.push([
|
attrs.push([
|
||||||
term::format::tertiary("Title".to_owned()),
|
term::format::tertiary("Title".to_owned()).into(),
|
||||||
term::format::bold(patch.title().to_owned()),
|
term::format::bold(patch.title().to_owned()).into(),
|
||||||
]);
|
]);
|
||||||
attrs.push([
|
attrs.push([
|
||||||
term::format::tertiary("Patch".to_owned()),
|
term::format::tertiary("Patch".to_owned()).into(),
|
||||||
term::format::default(patch_id.to_string()),
|
term::format::default(patch_id.to_string()).into(),
|
||||||
]);
|
]);
|
||||||
attrs.push([
|
attrs.push([
|
||||||
term::format::tertiary("Author".to_owned()),
|
term::format::tertiary("Author".to_owned()).into(),
|
||||||
term::format::default(patch.author().id().to_string()),
|
term::format::default(patch.author().id().to_string()).into(),
|
||||||
]);
|
]);
|
||||||
attrs.push([
|
attrs.push([
|
||||||
term::format::tertiary("Status".to_owned()),
|
term::format::tertiary("Head".to_owned()).into(),
|
||||||
|
term::format::secondary(revision.head().to_string()).into(),
|
||||||
|
]);
|
||||||
|
if !branches.is_empty() {
|
||||||
|
attrs.push([
|
||||||
|
term::format::tertiary("Branches".to_owned()).into(),
|
||||||
|
term::format::yellow(branches.join(", ")).into(),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
attrs.push([
|
||||||
|
term::format::tertiary("Commits".to_owned()).into(),
|
||||||
|
ahead_behind,
|
||||||
|
]);
|
||||||
|
attrs.push([
|
||||||
|
term::format::tertiary("Status".to_owned()).into(),
|
||||||
match state {
|
match state {
|
||||||
patch::State::Open => term::format::positive(state.to_string()),
|
patch::State::Open => term::format::positive(state.to_string()),
|
||||||
patch::State::Draft => term::format::dim(state.to_string()),
|
patch::State::Draft => term::format::dim(state.to_string()),
|
||||||
patch::State::Archived => term::format::yellow(state.to_string()),
|
patch::State::Archived => term::format::yellow(state.to_string()),
|
||||||
patch::State::Merged => term::format::primary(state.to_string()),
|
patch::State::Merged => term::format::primary(state.to_string()),
|
||||||
},
|
}
|
||||||
|
.into(),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
let description = patch.description().trim();
|
let description = patch.description().trim();
|
||||||
|
|
@ -95,7 +111,7 @@ pub fn run(
|
||||||
|
|
||||||
if diff {
|
if diff {
|
||||||
term::blank();
|
term::blank();
|
||||||
show_patch_diff(&patch, stored, workdir)?;
|
show_patch_diff(&patch, stored)?;
|
||||||
term::blank();
|
term::blank();
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::Paint;
|
use super::{Element, Line, Paint};
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
use unicode_width::UnicodeWidthStr;
|
||||||
|
|
||||||
|
|
@ -14,8 +14,10 @@ pub trait Cell: Display {
|
||||||
/// Cell display width in number of terminal columns.
|
/// Cell display width in number of terminal columns.
|
||||||
fn width(&self) -> usize;
|
fn width(&self) -> usize;
|
||||||
/// Truncate cell if longer than given width. Shows the delimiter if truncated.
|
/// Truncate cell if longer than given width. Shows the delimiter if truncated.
|
||||||
|
#[must_use]
|
||||||
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated;
|
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated;
|
||||||
/// Pad the cell so that it is the given width, while keeping the content left-aligned.
|
/// Pad the cell so that it is the given width, while keeping the content left-aligned.
|
||||||
|
#[must_use]
|
||||||
fn pad(&self, width: usize) -> Self::Padded;
|
fn pad(&self, width: usize) -> Self::Padded;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,6 +44,27 @@ impl Cell for Paint<String> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Cell for Line {
|
||||||
|
type Truncated = Line;
|
||||||
|
type Padded = Line;
|
||||||
|
|
||||||
|
fn width(&self) -> usize {
|
||||||
|
<Self as Element>::size(self).cols
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pad(&self, width: usize) -> Self::Padded {
|
||||||
|
let mut line = self.clone();
|
||||||
|
Line::pad(&mut line, width);
|
||||||
|
line
|
||||||
|
}
|
||||||
|
|
||||||
|
fn truncate(&self, width: usize, delim: &str) -> Self::Truncated {
|
||||||
|
let mut line = self.clone();
|
||||||
|
Line::truncate(&mut line, width, delim);
|
||||||
|
line
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Cell for Paint<&str> {
|
impl Cell for Paint<&str> {
|
||||||
type Truncated = Paint<String>;
|
type Truncated = Paint<String>;
|
||||||
type Padded = Paint<String>;
|
type Padded = Paint<String>;
|
||||||
|
|
|
||||||
|
|
@ -2,3 +2,6 @@ use crate::ansi::Color;
|
||||||
|
|
||||||
/// The faintest color; useful for borders and such.
|
/// The faintest color; useful for borders and such.
|
||||||
pub const FAINT: Color = Color::Fixed(236);
|
pub const FAINT: Color = Color::Fixed(236);
|
||||||
|
|
||||||
|
/// Negative color, useful for errors.
|
||||||
|
pub const NEGATIVE: Color = Color::Red;
|
||||||
|
|
|
||||||
|
|
@ -157,9 +157,9 @@ impl IntoIterator for Line {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<Label> for Line {
|
impl<T: Into<Label>> From<T> for Line {
|
||||||
fn from(label: Label) -> Self {
|
fn from(value: T) -> Self {
|
||||||
Self { items: vec![label] }
|
Self::new(value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -218,19 +218,19 @@ mod test {
|
||||||
let line = Line::default().item("banana").item("peach").item("apple");
|
let line = Line::default().item("banana").item("peach").item("apple");
|
||||||
|
|
||||||
let mut actual = line.clone();
|
let mut actual = line.clone();
|
||||||
actual.truncate(9, "…");
|
actual = actual.truncate(9, "…");
|
||||||
assert_eq!(actual.to_string(), "bananape…");
|
assert_eq!(actual.to_string(), "bananape…");
|
||||||
|
|
||||||
let mut actual = line.clone();
|
let mut actual = line.clone();
|
||||||
actual.truncate(7, "…");
|
actual = actual.truncate(7, "…");
|
||||||
assert_eq!(actual.to_string(), "banana…");
|
assert_eq!(actual.to_string(), "banana…");
|
||||||
|
|
||||||
let mut actual = line.clone();
|
let mut actual = line.clone();
|
||||||
actual.truncate(1, "…");
|
actual = actual.truncate(1, "…");
|
||||||
assert_eq!(actual.to_string(), "…");
|
assert_eq!(actual.to_string(), "…");
|
||||||
|
|
||||||
let mut actual = line;
|
let mut actual = line;
|
||||||
actual.truncate(0, "…");
|
actual = actual.truncate(0, "…");
|
||||||
assert_eq!(actual.to_string(), "");
|
assert_eq!(actual.to_string(), "");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ use std::fmt;
|
||||||
|
|
||||||
use crate as term;
|
use crate as term;
|
||||||
use crate::cell::Cell;
|
use crate::cell::Cell;
|
||||||
use crate::{Color, Label, Line, Max, Paint, Size};
|
use crate::{Color, Line, Max, Paint, Size};
|
||||||
|
|
||||||
pub use crate::Element;
|
pub use crate::Element;
|
||||||
|
|
||||||
|
|
@ -82,7 +82,7 @@ impl<const W: usize, T> Default for Table<W, T> {
|
||||||
|
|
||||||
impl<const W: usize, T: Cell + fmt::Debug> Element for Table<W, T>
|
impl<const W: usize, T: Cell + fmt::Debug> Element for Table<W, T>
|
||||||
where
|
where
|
||||||
T::Padded: Into<Label>,
|
T::Padded: Into<Line>,
|
||||||
{
|
{
|
||||||
fn size(&self) -> Size {
|
fn size(&self) -> Size {
|
||||||
Table::size(self)
|
Table::size(self)
|
||||||
|
|
@ -123,11 +123,11 @@ where
|
||||||
} else {
|
} else {
|
||||||
self.widths[i] + self.opts.spacing
|
self.widths[i] + self.opts.spacing
|
||||||
};
|
};
|
||||||
line.push(cell.pad(pad));
|
line = line.extend(cell.pad(pad).into());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(width) = width {
|
if let Some(width) = width {
|
||||||
line.truncate(width, "…");
|
line = line.truncate(width, "…");
|
||||||
}
|
}
|
||||||
if let Some(color) = border {
|
if let Some(color) = border {
|
||||||
line.push(Paint::new(" │").fg(color));
|
line.push(Paint::new(" │").fg(color));
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue