radicle_cli/commands/remote/
list.rs1use std::collections::HashSet;
2
3use radicle::git::Url;
4use radicle::identity::{Did, RepoId};
5use radicle::node::{Alias, AliasStore as _, NodeId};
6use radicle::storage::ReadStorage as _;
7use radicle::Profile;
8use radicle_term::{Element, Table};
9
10use crate::git;
11use crate::terminal as term;
12
13#[derive(Debug)]
14pub enum Direction {
15 Push(Url),
16 Fetch(Url),
17}
18
19#[derive(Debug)]
20pub struct Tracked {
21 name: String,
22 direction: Option<Direction>,
23}
24
25impl Tracked {
26 fn namespace(&self) -> Option<NodeId> {
27 match self.direction.as_ref()? {
28 Direction::Push(url) => url.namespace,
29 Direction::Fetch(url) => url.namespace,
30 }
31 }
32}
33
34#[derive(Debug)]
35pub struct Untracked {
36 remote: NodeId,
37 alias: Option<Alias>,
38}
39
40pub fn tracked(working: &git::Repository) -> anyhow::Result<Vec<Tracked>> {
41 Ok(git::rad_remotes(working)?
42 .into_iter()
43 .flat_map(|remote| {
44 [
45 Tracked {
46 name: remote.name.clone(),
47 direction: Some(Direction::Fetch(remote.url)),
48 },
49 Tracked {
50 name: remote.name,
51 direction: remote.pushurl.map(Direction::Push),
52 },
53 ]
54 })
55 .collect())
56}
57
58pub fn untracked<'a>(
59 rid: RepoId,
60 profile: &Profile,
61 tracked: impl Iterator<Item = &'a Tracked>,
62) -> anyhow::Result<Vec<Untracked>> {
63 let repo = profile.storage.repository(rid)?;
64 let aliases = profile.aliases();
65 let remotes = repo.remote_ids()?;
66 let git_remotes = tracked
67 .filter_map(|tracked| tracked.namespace())
68 .collect::<HashSet<_>>();
69 Ok(remotes
70 .filter_map(|remote| {
71 remote
72 .map(|remote| {
73 (!git_remotes.contains(&remote)).then_some(Untracked {
74 remote,
75 alias: aliases.alias(&remote),
76 })
77 })
78 .transpose()
79 })
80 .collect::<Result<Vec<_>, _>>()?)
81}
82
83pub fn print_tracked<'a>(tracked: impl Iterator<Item = &'a Tracked>) {
84 Table::from_iter(tracked.into_iter().flat_map(|Tracked { direction, name }| {
85 let Some(direction) = direction else {
86 return None;
87 };
88
89 let (dir, url) = match direction {
90 Direction::Push(url) => ("push", url),
91 Direction::Fetch(url) => ("fetch", url),
92 };
93 let description = url.namespace.map_or(
94 term::format::dim("(canonical upstream)".to_string()).italic(),
95 |namespace| term::format::tertiary(namespace.to_string()),
96 );
97 Some([
98 term::format::bold(name.clone()),
99 description,
100 term::format::parens(term::format::secondary(dir.to_owned())),
101 ])
102 }))
103 .print();
104}
105
106pub fn print_untracked<'a>(untracked: impl Iterator<Item = &'a Untracked>) {
107 Table::from_iter(untracked.into_iter().map(|Untracked { remote, alias }| {
108 [
109 match alias {
110 None => term::format::secondary("n/a".to_string()),
111 Some(alias) => term::format::secondary(alias.to_string()),
112 },
113 term::format::highlight(Did::from(remote).to_string()),
114 ]
115 }))
116 .print();
117}