1use git2::{self, Repository};
16use std::borrow::Borrow;
17use std::collections::HashMap;
18use std::iter::FromIterator;
19
20use issue;
21use repository::RepositoryExt;
22use trailer::{accumulation, spec};
23
24use error::*;
25use error::ErrorKind as EK;
26
27pub struct HeadRefsToIssuesIter<'r>
33{
34 inner: git2::References<'r>,
35 repo: &'r Repository
36}
37
38impl<'r> HeadRefsToIssuesIter<'r>
39{
40 pub fn new(repo: &'r Repository, inner: git2::References<'r>) -> Self {
41 HeadRefsToIssuesIter { inner: inner, repo: repo }
42 }
43}
44
45impl<'r> Iterator for HeadRefsToIssuesIter<'r>
46{
47 type Item = Result<issue::Issue<'r>>;
48
49 fn next(&mut self) -> Option<Self::Item> {
50 self.inner
51 .next()
52 .map(|reference| {
53 reference
54 .chain_err(|| EK::CannotGetReference)
55 .and_then(|r| self.repo.issue_by_head_ref(&r))
56 })
57 }
58}
59
60
61pub struct Messages<'r> {
67 pub(crate) revwalk: git2::Revwalk<'r>,
68 repo: &'r Repository,
69}
70
71impl<'r> Messages<'r> {
72 pub fn new<'a>(repo: &'a Repository, revwalk: git2::Revwalk<'a>) -> Messages<'a> {
75 Messages { revwalk: revwalk, repo: repo }
76 }
77
78 pub fn empty<'a>(repo: &'a Repository) -> Result<Messages<'a>> {
81 repo.revwalk()
82 .map(|revwalk| Self::new(repo, revwalk))
83 .chain_err(|| EK::CannotConstructRevwalk)
84 }
85
86 pub fn until_any_initial(self) -> IssueMessagesIter<'r> {
89 self.into()
90 }
91
92 pub fn terminate_at_initial(&mut self, issue: &issue::Issue) -> Result<()> {
99 for parent in issue.initial_message()?.parent_ids() {
100 self.revwalk.hide(parent)?;
101 }
102 Ok(())
103 }
104}
105
106impl<'r> Iterator for Messages<'r> {
107 type Item = Result<git2::Commit<'r>>;
108
109 fn next(&mut self) -> Option<Self::Item> {
110 self.revwalk
111 .next()
112 .map(|item| item
113 .and_then(|id| self.repo.find_commit(id))
114 .chain_err(|| EK::CannotGetCommit)
115 )
116 }
117}
118
119
120pub trait MessagesExt {
126 type Output:
127 accumulation::MultiAccumulator +
128 FromIterator<(String, accumulation::ValueAccumulator)>;
129
130 fn accumulate_trailers<'a, I, J>(self, specs: I) -> Self::Output
136 where I: IntoIterator<Item = J>,
137 J: Borrow<spec::TrailerSpec<'a>>;
138}
139
140impl<'a, I> MessagesExt for I
141 where I: Iterator<Item = git2::Commit<'a>>
142{
143 type Output = HashMap<String, accumulation::ValueAccumulator>;
144
145 fn accumulate_trailers<'b, J, K>(self, specs: J) -> Self::Output
146 where J: IntoIterator<Item = K>,
147 K: Borrow<spec::TrailerSpec<'b>>
148 {
149 use message::Message;
150 use trailer::accumulation::Accumulator;
151 use trailer::spec::ToMap;
152
153 let mut accumulator = specs.into_map();
154 accumulator.process_all(self.flat_map(|message| message.trailers()));
155 accumulator
156 }
157}
158
159
160pub struct IssueMessagesIter<'r>(Messages<'r>);
166
167impl<'r> IssueMessagesIter<'r> {
168 fn fuse_if_initial(&mut self, id: git2::Oid) {
171 if self.0.repo.find_issue(id).is_ok() {
172 self.0.revwalk.reset();
173 }
174 }
175}
176
177impl<'r> From<Messages<'r>> for IssueMessagesIter<'r> {
178 fn from(messages: Messages<'r>) -> Self {
179 IssueMessagesIter(messages)
180 }
181}
182
183impl<'r> Iterator for IssueMessagesIter<'r> {
184 type Item = Result<git2::Commit<'r>>;
185
186 fn next(&mut self) -> Option<Self::Item> {
187 self.0
188 .next()
189 .map(|item| {
190 if let Ok(ref commit) = item {
191 self.fuse_if_initial(commit.id());
192 }
193 item
194 })
195 }
196}
197
198
199pub struct RefsReferringTo<'r> {
210 refs: HashMap<git2::Oid, Vec<git2::Reference<'r>>>,
211 inner: git2::Revwalk<'r>,
212 current_refs: Vec<git2::Reference<'r>>,
213}
214
215impl<'r> RefsReferringTo<'r> {
216 pub fn new(messages: git2::Revwalk<'r>) -> Self
219 {
220 Self { refs: HashMap::new(), inner: messages, current_refs: Vec::new() }
221 }
222
223 pub fn push(&mut self, message: git2::Oid) -> Result<()> {
229 self.inner.push(message).chain_err(|| EK::CannotConstructRevwalk)
230 }
231
232 pub fn watch_ref(&mut self, reference: git2::Reference<'r>) -> Result<()> {
237 let id = reference
238 .peel(git2::ObjectType::Any)
239 .chain_err(|| EK::CannotGetCommitForRev(reference.name().unwrap_or_default().to_string()))?
240 .id();
241 self.refs.entry(id).or_insert_with(Vec::new).push(reference);
242 Ok(())
243 }
244
245 pub fn watch_refs<I>(&mut self, references: I) -> Result<()>
248 where I: IntoIterator<Item = git2::Reference<'r>>
249 {
250 for reference in references.into_iter() {
251 self.watch_ref(reference)?;
252 }
253 Ok(())
254 }
255}
256
257impl<'r> Iterator for RefsReferringTo<'r> {
258 type Item = Result<git2::Reference<'r>>;
259
260 fn next(&mut self) -> Option<Self::Item> {
261 'outer: loop {
262 if let Some(reference) = self.current_refs.pop() {
263 return Some(Ok(reference));
265 }
266
267 if self.refs.is_empty() {
270 return None;
271 }
272
273 for item in &mut self.inner {
275 match item.chain_err(|| EK::CannotGetCommit) {
276 Ok(id) => if let Some(new_refs) = self.refs.remove(&id) {
277 self.current_refs = new_refs;
280 continue 'outer;
281 },
282 Err(err) => return Some(Err(err)),
283 }
284 }
285
286 return None;
288 }
289 }
290}
291
292
293impl<'r> Extend<git2::Reference<'r>> for RefsReferringTo<'r> {
299 fn extend<I>(&mut self, references: I)
300 where I: IntoIterator<Item = git2::Reference<'r>>
301 {
302 self.current_refs.extend(references);
303 }
304}
305
306
307pub struct ReferenceDeletingIter<'r, I>
318 where I: Iterator<Item = git2::Reference<'r>>
319{
320 inner: I
321}
322
323impl<'r, I> ReferenceDeletingIter<'r, I>
324 where I: Iterator<Item = git2::Reference<'r>>
325{
326 pub fn delete_ignoring(self) {
332 for _ in self {}
333 }
334}
335
336impl<'r, I, J> From<J> for ReferenceDeletingIter<'r, I>
337 where I: Iterator<Item = git2::Reference<'r>>,
338 J: IntoIterator<Item = git2::Reference<'r>, IntoIter = I>
339{
340 fn from(items: J) -> Self {
341 ReferenceDeletingIter { inner: items.into_iter() }
342 }
343}
344
345impl<'r, I> Iterator for ReferenceDeletingIter<'r, I>
346 where I: Iterator<Item = git2::Reference<'r>>
347{
348 type Item = Error;
349
350 fn next(&mut self) -> Option<Self::Item> {
351 self.inner
352 .by_ref()
353 .filter_map(|mut r| r
354 .delete()
355 .chain_err(|| EK::CannotDeleteReference(r.name().unwrap_or_default().to_string()))
356 .err()
357 )
358 .next()
359 }
360}
361
362
363
364
365#[cfg(test)]
366mod tests {
367 use super::*;
368 use test_utils::TestingRepo;
369
370 use repository::RepositoryExt;
371
372 #[test]
375 fn referred_refs() {
376 let mut testing_repo = TestingRepo::new("referred_refs");
377 let repo = testing_repo.repo();
378
379 let sig = git2::Signature::now("Foo Bar", "foo.bar@example.com")
380 .expect("Could not create signature");
381 let empty_tree = repo
382 .empty_tree()
383 .expect("Could not create empty tree");
384 let empty_parents: Vec<&git2::Commit> = vec![];
385
386 let mut commits = repo.revwalk().expect("Could not create revwalk");
387 let mut refs_to_watch = Vec::new();
388 let mut refs_to_report = Vec::new();
389
390 {
391 let commit = repo
392 .commit(None, &sig, &sig, "Test message 1", &empty_tree, &empty_parents)
393 .expect("Could not create commit");
394 let refa = repo
395 .reference("refs/test/1a", commit, false, "create test ref 1a")
396 .expect("Could not create reference");
397 let refb = repo
398 .reference("refs/test/1b", commit, false, "create test ref 1b")
399 .expect("Could not create reference");
400 commits.push(commit).expect("Could not push commit onto revwalk");
401 refs_to_report.push(refa.name().expect("Could not retrieve name").to_string());
402 refs_to_report.push(refb.name().expect("Could not retrieve name").to_string());
403 refs_to_watch.push(refa);
404 refs_to_watch.push(refb);
405 }
406
407 {
408 let commit = repo
409 .commit(None, &sig, &sig, "Test message 2", &empty_tree, &empty_parents)
410 .expect("Could not create commit");
411 let refa = repo
412 .reference("refs/test/2a", commit, false, "create test ref 2a")
413 .expect("Could not create reference");
414 repo.reference("refs/test/2b", commit, false, "create test ref 2b")
415 .expect("Could not create reference");
416 commits.push(commit).expect("Could not push commit onto revwalk");
417 refs_to_report.push(refa.name().expect("Could not retrieve name").to_string());
418 refs_to_watch.push(refa);
419 }
420
421 {
422 let commit = repo
423 .commit(None, &sig, &sig, "Test message 3", &empty_tree, &empty_parents)
424 .expect("Could not create commit");
425 repo.reference("refs/test/3a", commit, false, "create test ref 3a")
426 .expect("Could not create reference");
427 repo.reference("refs/test/3b", commit, false, "create test ref 3b")
428 .expect("Could not create reference");
429 commits.push(commit).expect("Could not push commit onto revwalk");
430 }
431
432 {
433 let commit = repo
434 .commit(None, &sig, &sig, "Test message 4", &empty_tree, &empty_parents)
435 .expect("Could not create commit");
436 let refa = repo
437 .reference("refs/test/4a", commit, false, "create test ref 4a")
438 .expect("Could not create reference");
439 let refb = repo
440 .reference("refs/test/4b", commit, false, "create test ref 4b")
441 .expect("Could not create reference");
442 refs_to_watch.push(refa);
443 refs_to_watch.push(refb);
444 }
445
446 let mut referred = RefsReferringTo::new(commits);
447 referred.watch_refs(refs_to_watch).expect("Could not watch refs");
448
449 let mut reported: Vec<_> = referred
450 .map(|item| item
451 .expect("Error during iterating over refs")
452 .name()
453 .expect("Could not retrieve name")
454 .to_string()
455 )
456 .collect();
457 reported.sort();
458 refs_to_report.sort();
459 assert_eq!(reported, refs_to_report);
460 }
461}
462