radicle_surf/
refs.rs

1// I think the following `Tags` and `Branches` would be merged
2// using Generic associated types supported in Rust 1.65.0.
3
4use std::{
5    collections::{btree_set, BTreeSet},
6    convert::TryFrom as _,
7};
8
9use git_ext::ref_format::{self, lit, name::Components, Component, Qualified, RefString};
10
11use crate::{tag, Branch, Namespace, Tag};
12
13/// Iterator over [`Tag`]s.
14#[derive(Default)]
15pub struct Tags<'a> {
16    references: Vec<git2::References<'a>>,
17    current: usize,
18}
19
20/// Iterator over the [`Qualified`] names of [`Tag`]s.
21pub struct TagNames<'a> {
22    inner: Tags<'a>,
23}
24
25impl<'a> Tags<'a> {
26    pub(super) fn push(&mut self, references: git2::References<'a>) {
27        self.references.push(references)
28    }
29
30    pub fn names(self) -> TagNames<'a> {
31        TagNames { inner: self }
32    }
33}
34
35impl Iterator for Tags<'_> {
36    type Item = Result<Tag, error::Tag>;
37
38    fn next(&mut self) -> Option<Self::Item> {
39        while self.current < self.references.len() {
40            match self.references.get_mut(self.current) {
41                Some(refs) => match refs.next() {
42                    Some(res) => {
43                        return Some(
44                            res.map_err(error::Tag::from)
45                                .and_then(|r| Tag::try_from(&r).map_err(error::Tag::from)),
46                        );
47                    }
48                    None => self.current += 1,
49                },
50                None => break,
51            }
52        }
53        None
54    }
55}
56
57impl Iterator for TagNames<'_> {
58    type Item = Result<Qualified<'static>, error::Tag>;
59
60    fn next(&mut self) -> Option<Self::Item> {
61        while self.inner.current < self.inner.references.len() {
62            match self.inner.references.get_mut(self.inner.current) {
63                Some(refs) => match refs.next() {
64                    Some(res) => {
65                        return Some(res.map_err(error::Tag::from).and_then(|r| {
66                            tag::reference_name(&r)
67                                .map(|name| lit::refs_tags(name).into())
68                                .map_err(error::Tag::from)
69                        }))
70                    }
71                    None => self.inner.current += 1,
72                },
73                None => break,
74            }
75        }
76        None
77    }
78}
79
80/// Iterator over [`Branch`]es.
81#[derive(Default)]
82pub struct Branches<'a> {
83    references: Vec<git2::References<'a>>,
84    current: usize,
85}
86
87/// Iterator over the [`Qualified`] names of [`Branch`]es.
88pub struct BranchNames<'a> {
89    inner: Branches<'a>,
90}
91
92impl<'a> Branches<'a> {
93    pub(super) fn push(&mut self, references: git2::References<'a>) {
94        self.references.push(references)
95    }
96
97    pub fn names(self) -> BranchNames<'a> {
98        BranchNames { inner: self }
99    }
100}
101
102impl Iterator for Branches<'_> {
103    type Item = Result<Branch, error::Branch>;
104
105    fn next(&mut self) -> Option<Self::Item> {
106        while self.current < self.references.len() {
107            match self.references.get_mut(self.current) {
108                Some(refs) => match refs.next() {
109                    Some(res) => {
110                        return Some(
111                            res.map_err(error::Branch::from)
112                                .and_then(|r| Branch::try_from(&r).map_err(error::Branch::from)),
113                        )
114                    }
115                    None => self.current += 1,
116                },
117                None => break,
118            }
119        }
120        None
121    }
122}
123
124impl Iterator for BranchNames<'_> {
125    type Item = Result<Qualified<'static>, error::Branch>;
126
127    fn next(&mut self) -> Option<Self::Item> {
128        while self.inner.current < self.inner.references.len() {
129            match self.inner.references.get_mut(self.inner.current) {
130                Some(refs) => match refs.next() {
131                    Some(res) => {
132                        return Some(res.map_err(error::Branch::from).and_then(|r| {
133                            Branch::try_from(&r)
134                                .map(|branch| branch.refname().into_owned())
135                                .map_err(error::Branch::from)
136                        }))
137                    }
138                    None => self.inner.current += 1,
139                },
140                None => break,
141            }
142        }
143        None
144    }
145}
146
147// TODO: not sure this buys us much
148/// An iterator for namespaces.
149pub struct Namespaces {
150    namespaces: btree_set::IntoIter<Namespace>,
151}
152
153impl Namespaces {
154    pub(super) fn new(namespaces: BTreeSet<Namespace>) -> Self {
155        Self {
156            namespaces: namespaces.into_iter(),
157        }
158    }
159}
160
161impl Iterator for Namespaces {
162    type Item = Namespace;
163    fn next(&mut self) -> Option<Self::Item> {
164        self.namespaces.next()
165    }
166}
167
168#[derive(Default)]
169pub struct Categories<'a> {
170    references: Vec<git2::References<'a>>,
171    current: usize,
172}
173
174impl<'a> Categories<'a> {
175    pub(super) fn push(&mut self, references: git2::References<'a>) {
176        self.references.push(references)
177    }
178}
179
180impl Iterator for Categories<'_> {
181    type Item = Result<(RefString, RefString), error::Category>;
182
183    fn next(&mut self) -> Option<Self::Item> {
184        while self.current < self.references.len() {
185            match self.references.get_mut(self.current) {
186                Some(refs) => match refs.next() {
187                    Some(res) => {
188                        return Some(res.map_err(error::Category::from).and_then(|r| {
189                            let name = std::str::from_utf8(r.name_bytes())?;
190                            let name = ref_format::RefStr::try_from_str(name)?;
191                            let name = name.qualified().ok_or_else(|| {
192                                error::Category::NotQualified(name.to_ref_string())
193                            })?;
194                            let (_refs, category, c, cs) = name.non_empty_components();
195                            Ok((category.to_ref_string(), refstr_join(c, cs)))
196                        }));
197                    }
198                    None => self.current += 1,
199                },
200                None => break,
201            }
202        }
203        None
204    }
205}
206
207pub mod error {
208    use std::str;
209
210    use radicle_git_ext::ref_format::{self, RefString};
211    use thiserror::Error;
212
213    use crate::{branch, tag};
214
215    #[derive(Debug, Error)]
216    pub enum Branch {
217        #[error(transparent)]
218        Git(#[from] git2::Error),
219        #[error(transparent)]
220        Branch(#[from] branch::error::Branch),
221    }
222
223    #[derive(Debug, Error)]
224    pub enum Category {
225        #[error(transparent)]
226        Git(#[from] git2::Error),
227        #[error("the reference '{0}' was expected to be qualified, i.e. 'refs/<category>/<path>'")]
228        NotQualified(RefString),
229        #[error(transparent)]
230        RefFormat(#[from] ref_format::Error),
231        #[error(transparent)]
232        Utf8(#[from] str::Utf8Error),
233    }
234
235    #[derive(Debug, Error)]
236    pub enum Tag {
237        #[error(transparent)]
238        Git(#[from] git2::Error),
239        #[error(transparent)]
240        Tag(#[from] tag::error::FromReference),
241    }
242}
243
244pub(crate) fn refstr_join<'a>(c: Component<'a>, cs: Components<'a>) -> RefString {
245    std::iter::once(c).chain(cs).collect::<RefString>()
246}