diff --git a/radicle-cli/examples/rad-clone-all.md b/radicle-cli/examples/rad-clone-all.md index 5b5b5337..d1846840 100644 --- a/radicle-cli/examples/rad-clone-all.md +++ b/radicle-cli/examples/rad-clone-all.md @@ -31,28 +31,27 @@ Let's check that we have all the namespaces in storage: ``` $ rad inspect --refs -. -|-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi -| `-- refs -| |-- heads -| | `-- master -| `-- rad -| |-- id -| `-- sigrefs -|-- z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk -| `-- refs -| |-- heads -| | `-- master -| `-- rad -| |-- id -| `-- sigrefs -`-- z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z - `-- refs - |-- heads - | `-- master - `-- rad - |-- id - `-- sigrefs +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs +z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs +z6Mkux1aUQD2voWWukVb5nNUR7thrHveQG4pDQua8nVhib7Z +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs ``` We can then setup a git remote for `bob`: diff --git a/radicle-cli/examples/rad-fork.md b/radicle-cli/examples/rad-fork.md index 8e38e908..c4c4fb5d 100644 --- a/radicle-cli/examples/rad-fork.md +++ b/radicle-cli/examples/rad-fork.md @@ -5,14 +5,13 @@ NID. This is demonstrated below where our NID is ``` $ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --refs -. -`-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi - `-- refs - |-- heads - | `-- master - `-- rad - |-- id - `-- sigrefs +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs ``` To remedy this, we can use the `rad fork` command for the project we @@ -28,21 +27,20 @@ have a copy of the main set of refs: ``` $ rad inspect rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --refs -. -|-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi -| `-- refs -| |-- heads -| | `-- master -| `-- rad -| |-- id -| `-- sigrefs -`-- z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk - `-- refs - |-- heads - | `-- master - `-- rad - |-- id - `-- sigrefs +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs +z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs ``` We are now able to setup a remote in our own working copy of the diff --git a/radicle-cli/examples/rad-inspect.md b/radicle-cli/examples/rad-inspect.md index 8e07872c..79da7e87 100644 --- a/radicle-cli/examples/rad-inspect.md +++ b/radicle-cli/examples/rad-inspect.md @@ -17,14 +17,13 @@ It's also possible to display all of the repository's git references: ``` $ rad inspect --refs -. -`-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi - `-- refs - |-- heads - | `-- master - `-- rad - |-- id - `-- sigrefs +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs ``` Or display the repository identity's payload: diff --git a/radicle-cli/examples/rad-merge-via-push.md b/radicle-cli/examples/rad-merge-via-push.md index c60cfd40..04e2b951 100644 --- a/radicle-cli/examples/rad-merge-via-push.md +++ b/radicle-cli/examples/rad-merge-via-push.md @@ -30,21 +30,20 @@ And some remote refs: ``` $ rad inspect --refs -. -`-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi - `-- refs - |-- cobs - | `-- xyz.radicle.patch - | |-- 0ec956c94256fa101db4c32956ce195a1aa0edf2 - | `-- 928d76e22ef98a8406f2e4e4bcc8878533bbdfe0 - |-- heads - | |-- master - | `-- patches - | |-- 0ec956c94256fa101db4c32956ce195a1aa0edf2 - | `-- 928d76e22ef98a8406f2e4e4bcc8878533bbdfe0 - `-- rad - |-- id - `-- sigrefs +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +└── refs +    ├── cobs +    │   └── xyz.radicle.patch +    │      ├── 0ec956c94256fa101db4c32956ce195a1aa0edf2 +    │      └── 928d76e22ef98a8406f2e4e4bcc8878533bbdfe0 +    ├── heads +    │   ├── master +    │   └── patches +    │      ├── 0ec956c94256fa101db4c32956ce195a1aa0edf2 +    │      └── 928d76e22ef98a8406f2e4e4bcc8878533bbdfe0 +    └── rad +       ├── id +       └── sigrefs ``` Then let's merge the changes into `master`. @@ -86,16 +85,15 @@ And so were the remote branches: ``` $ rad inspect --refs -. -`-- z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi - `-- refs - |-- cobs - | `-- xyz.radicle.patch - | |-- 0ec956c94256fa101db4c32956ce195a1aa0edf2 - | `-- 928d76e22ef98a8406f2e4e4bcc8878533bbdfe0 - |-- heads - | `-- master - `-- rad - |-- id - `-- sigrefs +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +└── refs +    ├── cobs +    │   └── xyz.radicle.patch +    │      ├── 0ec956c94256fa101db4c32956ce195a1aa0edf2 +    │      └── 928d76e22ef98a8406f2e4e4bcc8878533bbdfe0 +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs ``` diff --git a/radicle-cli/src/commands/inspect.rs b/radicle-cli/src/commands/inspect.rs index 95ddf845..32d0e480 100644 --- a/radicle-cli/src/commands/inspect.rs +++ b/radicle-cli/src/commands/inspect.rs @@ -1,7 +1,7 @@ #![allow(clippy::or_fun_call)] +use std::collections::HashMap; use std::ffi::OsString; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; use std::str::FromStr; use anyhow::{anyhow, Context as _}; @@ -11,6 +11,7 @@ use json_color::{Color, Colorizer}; use radicle::crypto::{Unverified, Verified}; use radicle::identity::Untrusted; use radicle::identity::{Doc, Id}; +use radicle::storage::git::Storage; use radicle::storage::{ReadRepository, ReadStorage}; use crate::terminal as term; @@ -34,7 +35,7 @@ Options --id Return the repository identifier (RID) --payload Inspect the repository's identity payload - --refs Inspect the repository's refs on the local device (requires `tree`) + --refs Inspect the repository's refs on the local device --history Show the history of the repository identity document --help Print help "#, @@ -127,15 +128,7 @@ pub fn run(options: Options, ctx: impl term::Context) -> anyhow::Result<()> { match options.target { Target::Refs => { - let path = storage.path_of(&id).join("refs").join("namespaces"); - - Command::new("tree") - .current_dir(path) - .args(["--noreport", "--prune"]) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .spawn()? - .wait()?; + refs(storage, id)?; } Target::Payload => { println!( @@ -216,3 +209,116 @@ fn colorizer() -> Colorizer { .key(Color::Blue) .build() } + +fn refs(storage: &Storage, id: Id) -> anyhow::Result<()> { + let repo = storage.repository(id)?; + let mut refs = Vec::new(); + for r in repo.references()? { + let r = r?; + if let Some(namespace) = r.namespace { + refs.push(format!("{}/{}", namespace, r.name)); + } + } + + print!("{}", tree(refs)); + + Ok(()) +} + +/// Show the list of given git references as a newline terminated tree `String` similar to the tree command. +fn tree(mut refs: Vec) -> String { + refs.sort(); + + // List of references with additional unique entries for each 'directory'. + // + // i.e. "refs/heads/master" becomes ["refs"], ["refs", "heads"], and ["refs", "heads", + // "master"]. + let mut refs_expanded: Vec> = Vec::new(); + // Number of entries per Git 'directory'. + let mut ref_entries: HashMap, usize> = HashMap::new(); + let mut last: Vec = Vec::new(); + + for r in refs { + let r: Vec = r.split('/').map(|s| s.to_string()).collect(); + + for (i, v) in r.iter().enumerate() { + let last_v = last.get(i); + if Some(v) != last_v { + last = r.clone().iter().take(i + 1).map(String::from).collect(); + + refs_expanded.push(last.clone()); + + let mut dir = last.clone(); + dir.pop(); + if dir.is_empty() { + continue; + } + + if let Some(num) = ref_entries.get_mut(&dir) { + *num += 1; + } else { + ref_entries.insert(dir, 1); + } + } + } + } + let mut tree = String::default(); + + for mut ref_components in refs_expanded { + // Better to explode when things do not go as expected. + let name = ref_components.pop().expect("non-empty vector"); + if ref_components.is_empty() { + tree.push_str(&format!("{name}\n")); + continue; + } + + for i in 1..ref_components.len() { + let parent: Vec = ref_components.iter().take(i).cloned().collect(); + + let num = ref_entries.get(&parent).unwrap_or(&0); + if *num == 0 { + tree.push_str("    "); + } else { + tree.push_str("│   "); + } + } + + if let Some(num) = ref_entries.get_mut(&ref_components) { + if *num == 1 { + tree.push_str(&format!("└── {name}\n")); + } else { + tree.push_str(&format!("├── {name}\n")); + } + *num -= 1; + } + } + + tree +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_tree() { + let arg = vec![ + String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/refs/heads/master"), + String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/refs/rad/id"), + String::from("z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/refs/rad/sigrefs"), + ]; + let exp = r#" +z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi +└── refs +    ├── heads +    │   └── master +    └── rad +       ├── id +       └── sigrefs +"# + .trim_start(); + + assert_eq!(tree(arg), exp); + assert_eq!(tree(vec![String::new()]), "\n"); + } +}