cli: Improve `rad inspect`
* Don't always require auth * Fix `--refs` sub-command
This commit is contained in:
parent
892f02bc43
commit
2a687502c5
|
|
@ -0,0 +1,13 @@
|
||||||
|
The `rad inspect` command can be run without being authenticated with radicle:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad self
|
||||||
|
✗ Self failed: Could not load radicle profile
|
||||||
|
✗ To setup your radicle profile, run `rad auth`.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad inspect
|
||||||
|
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||||
|
```
|
||||||
|
|
@ -0,0 +1,70 @@
|
||||||
|
To display a repository's identifier, or *RID*, you may use the `rad inspect`
|
||||||
|
command from inside a working copy:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad inspect
|
||||||
|
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||||
|
```
|
||||||
|
|
||||||
|
As a shorthand, you can also simply use `rad .`:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad .
|
||||||
|
rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
|
||||||
|
```
|
||||||
|
|
||||||
|
It's also possible to display all of the repository's git references:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad inspect --refs
|
||||||
|
.
|
||||||
|
`-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
|
||||||
|
`-- refs
|
||||||
|
|-- heads
|
||||||
|
| `-- master
|
||||||
|
`-- rad
|
||||||
|
|-- id
|
||||||
|
`-- sigrefs
|
||||||
|
```
|
||||||
|
|
||||||
|
Or display the repository identity's payload:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad inspect --payload
|
||||||
|
{
|
||||||
|
"xyz.radicle.project": {
|
||||||
|
"defaultBranch": "master",
|
||||||
|
"description": "Radicle Heartwood Protocol & Stack",
|
||||||
|
"name": "heartwood"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Finally, the `--history` flag allows you to examine the identity document's
|
||||||
|
history:
|
||||||
|
|
||||||
|
```
|
||||||
|
$ rad inspect --history
|
||||||
|
commit 175267b8910895ba87760313af254c2900743912
|
||||||
|
blob d96f425412c9f8ad5d9a9a05c9831d0728e2338d
|
||||||
|
date Thu, 15 Dec 2022 17:28:04 +0000
|
||||||
|
|
||||||
|
Initialize Radicle
|
||||||
|
|
||||||
|
Rad-Signature: z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi z5nGqUvrmfiSyLjNCHWTWYvVMcPUZcvo9TxPKzEKXYBdSgUzbrqf1cYsmpGgbQvYunnsrLSsubEmxZaRdKM4quqQR
|
||||||
|
|
||||||
|
{
|
||||||
|
"delegates": [
|
||||||
|
"did:key:z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi"
|
||||||
|
],
|
||||||
|
"payload": {
|
||||||
|
"xyz.radicle.project": {
|
||||||
|
"defaultBranch": "master",
|
||||||
|
"description": "Radicle Heartwood Protocol & Stack",
|
||||||
|
"name": "heartwood"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"threshold": 1
|
||||||
|
}
|
||||||
|
|
||||||
|
```
|
||||||
|
|
@ -18,35 +18,41 @@ use crate::terminal::args::{Args, Error, Help};
|
||||||
|
|
||||||
pub const HELP: Help = Help {
|
pub const HELP: Help = Help {
|
||||||
name: "inspect",
|
name: "inspect",
|
||||||
description: "Inspect an identity or project directory",
|
description: "Inspect a radicle repository",
|
||||||
version: env!("CARGO_PKG_VERSION"),
|
version: env!("CARGO_PKG_VERSION"),
|
||||||
usage: r#"
|
usage: r#"
|
||||||
Usage
|
Usage
|
||||||
|
|
||||||
rad inspect <path> [<option>...]
|
rad inspect <path> [<option>...]
|
||||||
rad inspect <id> [<option>...]
|
rad inspect <rid> [<option>...]
|
||||||
rad inspect
|
rad inspect
|
||||||
|
|
||||||
Inspects the given path or ID. If neither is specified,
|
Inspects the given path or RID. If neither is specified,
|
||||||
the current project is inspected.
|
the current repository is inspected.
|
||||||
|
|
||||||
Options
|
Options
|
||||||
|
|
||||||
--id Return the ID in simplified form
|
--id Return the repository identifier (RID)
|
||||||
--payload Inspect the object's payload
|
--payload Inspect the repository's identity payload
|
||||||
--refs Inspect the object's refs on the local device (requires `tree`)
|
--refs Inspect the repository's refs on the local device (requires `tree`)
|
||||||
--history Show object's history
|
--history Show the history of the repository identity document
|
||||||
--help Print help
|
--help Print help
|
||||||
"#,
|
"#,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Eq, PartialEq)]
|
||||||
|
pub enum Target {
|
||||||
|
Refs,
|
||||||
|
Payload,
|
||||||
|
History,
|
||||||
|
#[default]
|
||||||
|
Id,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default, Debug, Eq, PartialEq)]
|
#[derive(Default, Debug, Eq, PartialEq)]
|
||||||
pub struct Options {
|
pub struct Options {
|
||||||
pub id: Option<Id>,
|
pub id: Option<Id>,
|
||||||
pub refs: bool,
|
pub target: Target,
|
||||||
pub payload: bool,
|
|
||||||
pub history: bool,
|
|
||||||
pub id_only: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Args for Options {
|
impl Args for Options {
|
||||||
|
|
@ -55,10 +61,7 @@ impl Args for Options {
|
||||||
|
|
||||||
let mut parser = lexopt::Parser::from_args(args);
|
let mut parser = lexopt::Parser::from_args(args);
|
||||||
let mut id: Option<Id> = None;
|
let mut id: Option<Id> = None;
|
||||||
let mut refs = false;
|
let mut target = Target::default();
|
||||||
let mut payload = false;
|
|
||||||
let mut history = false;
|
|
||||||
let mut id_only = false;
|
|
||||||
|
|
||||||
while let Some(arg) = parser.next()? {
|
while let Some(arg) = parser.next()? {
|
||||||
match arg {
|
match arg {
|
||||||
|
|
@ -66,16 +69,16 @@ impl Args for Options {
|
||||||
return Err(Error::Help.into());
|
return Err(Error::Help.into());
|
||||||
}
|
}
|
||||||
Long("refs") => {
|
Long("refs") => {
|
||||||
refs = true;
|
target = Target::Refs;
|
||||||
}
|
}
|
||||||
Long("payload") => {
|
Long("payload") => {
|
||||||
payload = true;
|
target = Target::Payload;
|
||||||
}
|
}
|
||||||
Long("history") => {
|
Long("history") => {
|
||||||
history = true;
|
target = Target::History;
|
||||||
}
|
}
|
||||||
Long("id") => {
|
Long("id") => {
|
||||||
id_only = true;
|
target = Target::Id;
|
||||||
}
|
}
|
||||||
Value(val) if id.is_none() => {
|
Value(val) if id.is_none() => {
|
||||||
let val = val.to_string_lossy();
|
let val = val.to_string_lossy();
|
||||||
|
|
@ -94,24 +97,11 @@ impl Args for Options {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok((
|
Ok((Options { id, target }, vec![]))
|
||||||
Options {
|
|
||||||
id,
|
|
||||||
payload,
|
|
||||||
history,
|
|
||||||
refs,
|
|
||||||
id_only,
|
|
||||||
},
|
|
||||||
vec![],
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
let profile = ctx.profile()?;
|
|
||||||
let storage = &profile.storage;
|
|
||||||
let signer = term::signer(&profile)?;
|
|
||||||
|
|
||||||
let id = match options.id {
|
let id = match options.id {
|
||||||
Some(id) => id,
|
Some(id) => id,
|
||||||
None => {
|
None => {
|
||||||
|
|
@ -122,17 +112,21 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if options.target == Target::Id {
|
||||||
|
term::info!("{}", term::format::highlight(id.urn()));
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
let profile = ctx.profile()?;
|
||||||
|
let storage = &profile.storage;
|
||||||
|
let signer = term::signer(&profile)?;
|
||||||
let project = storage
|
let project = storage
|
||||||
.get(signer.public_key(), id)?
|
.get(signer.public_key(), id)?
|
||||||
.context("No project with such `id` exists")?;
|
.context("No project with such `id` exists")?;
|
||||||
|
|
||||||
if options.refs {
|
match options.target {
|
||||||
let path = profile
|
Target::Refs => {
|
||||||
.home
|
let path = storage.path_of(&id).join("refs").join("namespaces");
|
||||||
.storage()
|
|
||||||
.join(id.urn())
|
|
||||||
.join("refs")
|
|
||||||
.join("namespaces");
|
|
||||||
|
|
||||||
Command::new("tree")
|
Command::new("tree")
|
||||||
.current_dir(path)
|
.current_dir(path)
|
||||||
|
|
@ -141,12 +135,14 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
.stderr(Stdio::inherit())
|
.stderr(Stdio::inherit())
|
||||||
.spawn()?
|
.spawn()?
|
||||||
.wait()?;
|
.wait()?;
|
||||||
} else if options.payload {
|
}
|
||||||
|
Target::Payload => {
|
||||||
println!(
|
println!(
|
||||||
"{}",
|
"{}",
|
||||||
colorizer().colorize_json_str(&serde_json::to_string_pretty(&project.payload)?)?
|
colorizer().colorize_json_str(&serde_json::to_string_pretty(&project.payload)?)?
|
||||||
);
|
);
|
||||||
} else if options.history {
|
}
|
||||||
|
Target::History => {
|
||||||
let repo = storage.repository(id)?;
|
let repo = storage.repository(id)?;
|
||||||
let head = Doc::<Untrusted>::head(signer.public_key(), &repo)?;
|
let head = Doc::<Untrusted>::head(signer.public_key(), &repo)?;
|
||||||
let history = repo.revwalk(head)?;
|
let history = repo.revwalk(head)?;
|
||||||
|
|
@ -164,7 +160,8 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
FixedOffset::west(tip.time().offset_minutes() * 60)
|
FixedOffset::west(tip.time().offset_minutes() * 60)
|
||||||
};
|
};
|
||||||
let time = DateTime::<Utc>::from(
|
let time = DateTime::<Utc>::from(
|
||||||
std::time::UNIX_EPOCH + std::time::Duration::from_secs(tip.time().seconds() as u64),
|
std::time::UNIX_EPOCH
|
||||||
|
+ std::time::Duration::from_secs(tip.time().seconds() as u64),
|
||||||
)
|
)
|
||||||
.with_timezone(&timezone)
|
.with_timezone(&timezone)
|
||||||
.to_rfc2822();
|
.to_rfc2822();
|
||||||
|
|
@ -183,21 +180,26 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> {
|
||||||
|
|
||||||
if let Some(msg) = tip.message() {
|
if let Some(msg) = tip.message() {
|
||||||
for line in msg.lines() {
|
for line in msg.lines() {
|
||||||
|
if line.is_empty() {
|
||||||
|
println!();
|
||||||
|
} else {
|
||||||
term::indented(term::format::dim(line));
|
term::indented(term::format::dim(line));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
term::blank();
|
term::blank();
|
||||||
}
|
}
|
||||||
|
|
||||||
let json = colorizer().colorize_json_str(&serde_json::to_string_pretty(&content)?)?;
|
let json =
|
||||||
|
colorizer().colorize_json_str(&serde_json::to_string_pretty(&content)?)?;
|
||||||
for line in json.lines() {
|
for line in json.lines() {
|
||||||
println!(" {line}");
|
println!(" {line}");
|
||||||
}
|
}
|
||||||
println!();
|
println!();
|
||||||
}
|
}
|
||||||
} else if options.id_only {
|
}
|
||||||
term::info!("{}", term::format::highlight(id.urn()));
|
Target::Id => {
|
||||||
} else {
|
// Handled above.
|
||||||
term::info!("{}", term::format::highlight(id));
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ use super::Error;
|
||||||
use super::{style, Paint};
|
use super::{style, Paint};
|
||||||
|
|
||||||
pub const ERROR_PREFIX: Paint<&str> = Paint::red("✗");
|
pub const ERROR_PREFIX: Paint<&str> = Paint::red("✗");
|
||||||
|
pub const ERROR_HINT_PREFIX: Paint<&str> = Paint::yellow("✗");
|
||||||
pub const WARNING_PREFIX: Paint<&str> = Paint::yellow("!");
|
pub const WARNING_PREFIX: Paint<&str> = Paint::yellow("!");
|
||||||
pub const TAB: &str = " ";
|
pub const TAB: &str = " ";
|
||||||
|
|
||||||
|
|
@ -151,7 +152,7 @@ pub fn fail(header: &str, error: &anyhow::Error) {
|
||||||
);
|
);
|
||||||
|
|
||||||
if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::<Error>() {
|
if let Some(Error::WithHint { hint, .. }) = error.downcast_ref::<Error>() {
|
||||||
println!("{} {}", Paint::yellow("×"), Paint::yellow(hint));
|
println!("{} {}", ERROR_HINT_PREFIX, Paint::yellow(hint));
|
||||||
blank();
|
blank();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -96,6 +96,34 @@ fn rad_init() {
|
||||||
.unwrap();
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rad_inspect() {
|
||||||
|
let mut environment = Environment::new();
|
||||||
|
let profile = environment.profile("alice");
|
||||||
|
let working = tempfile::tempdir().unwrap();
|
||||||
|
|
||||||
|
// Setup a test repository.
|
||||||
|
fixtures::repository(working.path());
|
||||||
|
|
||||||
|
test(
|
||||||
|
"examples/rad-init.md",
|
||||||
|
working.path(),
|
||||||
|
Some(&profile.home),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
test(
|
||||||
|
"examples/rad-inspect.md",
|
||||||
|
working.path(),
|
||||||
|
Some(&profile.home),
|
||||||
|
[],
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
test("examples/rad-inspect-noauth.md", working.path(), None, []).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn rad_checkout() {
|
fn rad_checkout() {
|
||||||
let mut environment = Environment::new();
|
let mut environment = Environment::new();
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ pub mod refs;
|
||||||
|
|
||||||
use std::collections::hash_map;
|
use std::collections::hash_map;
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
use std::{fmt, io};
|
use std::{fmt, io};
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
@ -262,6 +262,8 @@ pub trait ReadStorage {
|
||||||
|
|
||||||
/// Get the storage base path.
|
/// Get the storage base path.
|
||||||
fn path(&self) -> &Path;
|
fn path(&self) -> &Path;
|
||||||
|
/// Get a repository's path.
|
||||||
|
fn path_of(&self, rid: &Id) -> PathBuf;
|
||||||
/// Get an identity document of a repository under a given remote.
|
/// Get an identity document of a repository under a given remote.
|
||||||
fn get(
|
fn get(
|
||||||
&self,
|
&self,
|
||||||
|
|
@ -380,6 +382,10 @@ where
|
||||||
self.deref().path()
|
self.deref().path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_of(&self, rid: &Id) -> PathBuf {
|
||||||
|
self.deref().path_of(rid)
|
||||||
|
}
|
||||||
|
|
||||||
fn contains(&self, rid: &Id) -> Result<bool, ProjectError> {
|
fn contains(&self, rid: &Id) -> Result<bool, ProjectError> {
|
||||||
self.deref().contains(rid)
|
self.deref().contains(rid)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -101,6 +101,10 @@ impl ReadStorage for Storage {
|
||||||
self.path.as_path()
|
self.path.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_of(&self, rid: &Id) -> PathBuf {
|
||||||
|
paths::repository(&self, rid)
|
||||||
|
}
|
||||||
|
|
||||||
fn contains(&self, rid: &Id) -> Result<bool, ProjectError> {
|
fn contains(&self, rid: &Id) -> Result<bool, ProjectError> {
|
||||||
if paths::repository(&self, rid).exists() {
|
if paths::repository(&self, rid).exists() {
|
||||||
let _ = self.repository(*rid)?.head()?;
|
let _ = self.repository(*rid)?.head()?;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,10 @@ impl ReadStorage for MockStorage {
|
||||||
self.path.as_path()
|
self.path.as_path()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn path_of(&self, rid: &Id) -> PathBuf {
|
||||||
|
self.path().join(rid.canonical())
|
||||||
|
}
|
||||||
|
|
||||||
fn contains(&self, rid: &Id) -> Result<bool, ProjectError> {
|
fn contains(&self, rid: &Id) -> Result<bool, ProjectError> {
|
||||||
Ok(self.inventory.contains_key(rid))
|
Ok(self.inventory.contains_key(rid))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue