radicle_cli/commands/remote/
list.rs

1use 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}