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 let mut table = Table::default();
85 for Tracked { direction, name } in tracked {
86 let Some(direction) = direction else {
87 continue;
88 };
89
90 let (dir, url) = match direction {
91 Direction::Push(url) => ("push", url),
92 Direction::Fetch(url) => ("fetch", url),
93 };
94 let description = url.namespace.map_or(
95 term::format::dim("(canonical upstream)".to_string()).italic(),
96 |namespace| term::format::tertiary(namespace.to_string()),
97 );
98 table.push([
99 term::format::bold(name.clone()),
100 description,
101 term::format::parens(term::format::secondary(dir.to_owned())),
102 ]);
103 }
104 table.print();
105}
106
107pub fn print_untracked<'a>(untracked: impl Iterator<Item = &'a Untracked>) {
108 let mut t = Table::default();
109 for Untracked { remote, alias } in untracked {
110 t.push([
111 match alias {
112 None => term::format::secondary("n/a".to_string()),
113 Some(alias) => term::format::secondary(alias.to_string()),
114 },
115 term::format::highlight(Did::from(remote).to_string()),
116 ])
117 }
118 t.print();
119}