1use 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#[derive(Default)]
15pub struct Tags<'a> {
16 references: Vec<git2::References<'a>>,
17 current: usize,
18}
19
20pub 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#[derive(Default)]
82pub struct Branches<'a> {
83 references: Vec<git2::References<'a>>,
84 current: usize,
85}
86
87pub 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
147pub 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}