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