use git2::{self, Repository};
use std::borrow::Borrow;
use std::collections::HashMap;
use std::iter::FromIterator;
use issue;
use repository::RepositoryExt;
use trailer::{accumulation, spec};
use error::*;
use error::ErrorKind as EK;
pub struct HeadRefsToIssuesIter<'r>
{
inner: git2::References<'r>,
repo: &'r Repository
}
impl<'r> HeadRefsToIssuesIter<'r>
{
pub fn new(repo: &'r Repository, inner: git2::References<'r>) -> Self {
HeadRefsToIssuesIter { inner: inner, repo: repo }
}
}
impl<'r> Iterator for HeadRefsToIssuesIter<'r>
{
type Item = Result<issue::Issue<'r>>;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.next()
.map(|reference| {
reference
.chain_err(|| EK::CannotGetReference)
.and_then(|r| self.repo.issue_by_head_ref(&r))
})
}
}
pub struct Messages<'r> {
pub(crate) revwalk: git2::Revwalk<'r>,
repo: &'r Repository,
}
impl<'r> Messages<'r> {
pub fn new<'a>(repo: &'a Repository, revwalk: git2::Revwalk<'a>) -> Messages<'a> {
Messages { revwalk: revwalk, repo: repo }
}
pub fn empty<'a>(repo: &'a Repository) -> Result<Messages<'a>> {
repo.revwalk()
.map(|revwalk| Self::new(repo, revwalk))
.chain_err(|| EK::CannotConstructRevwalk)
}
pub fn until_any_initial(self) -> IssueMessagesIter<'r> {
self.into()
}
pub fn terminate_at_initial(&mut self, issue: &issue::Issue) -> Result<()> {
for parent in issue.initial_message()?.parent_ids() {
self.revwalk.hide(parent)?;
}
Ok(())
}
}
impl<'r> Iterator for Messages<'r> {
type Item = Result<git2::Commit<'r>>;
fn next(&mut self) -> Option<Self::Item> {
self.revwalk
.next()
.map(|item| item
.and_then(|id| self.repo.find_commit(id))
.chain_err(|| EK::CannotGetCommit)
)
}
}
pub trait MessagesExt {
type Output:
accumulation::MultiAccumulator +
FromIterator<(String, accumulation::ValueAccumulator)>;
fn accumulate_trailers<'a, I, J>(self, specs: I) -> Self::Output
where I: IntoIterator<Item = J>,
J: Borrow<spec::TrailerSpec<'a>>;
}
impl<'a, I> MessagesExt for I
where I: Iterator<Item = git2::Commit<'a>>
{
type Output = HashMap<String, accumulation::ValueAccumulator>;
fn accumulate_trailers<'b, J, K>(self, specs: J) -> Self::Output
where J: IntoIterator<Item = K>,
K: Borrow<spec::TrailerSpec<'b>>
{
use message::Message;
use trailer::accumulation::Accumulator;
use trailer::spec::ToMap;
let mut accumulator = specs.into_map();
accumulator.process_all(self.flat_map(|message| message.trailers()));
accumulator
}
}
pub struct IssueMessagesIter<'r>(Messages<'r>);
impl<'r> IssueMessagesIter<'r> {
fn fuse_if_initial(&mut self, id: git2::Oid) {
if self.0.repo.find_issue(id).is_ok() {
self.0.revwalk.reset();
}
}
}
impl<'r> From<Messages<'r>> for IssueMessagesIter<'r> {
fn from(messages: Messages<'r>) -> Self {
IssueMessagesIter(messages)
}
}
impl<'r> Iterator for IssueMessagesIter<'r> {
type Item = Result<git2::Commit<'r>>;
fn next(&mut self) -> Option<Self::Item> {
self.0
.next()
.map(|item| {
if let Ok(ref commit) = item {
self.fuse_if_initial(commit.id());
}
item
})
}
}
pub struct RefsReferringTo<'r> {
refs: HashMap<git2::Oid, Vec<git2::Reference<'r>>>,
inner: git2::Revwalk<'r>,
current_refs: Vec<git2::Reference<'r>>,
}
impl<'r> RefsReferringTo<'r> {
pub fn new(messages: git2::Revwalk<'r>) -> Self
{
Self { refs: HashMap::new(), inner: messages, current_refs: Vec::new() }
}
pub fn push(&mut self, message: git2::Oid) -> Result<()> {
self.inner.push(message).chain_err(|| EK::CannotConstructRevwalk)
}
pub fn watch_ref(&mut self, reference: git2::Reference<'r>) -> Result<()> {
let id = reference
.peel(git2::ObjectType::Any)
.chain_err(|| EK::CannotGetCommitForRev(reference.name().unwrap_or_default().to_string()))?
.id();
self.refs.entry(id).or_insert_with(Vec::new).push(reference);
Ok(())
}
pub fn watch_refs<I>(&mut self, references: I) -> Result<()>
where I: IntoIterator<Item = git2::Reference<'r>>
{
for reference in references.into_iter() {
self.watch_ref(reference)?;
}
Ok(())
}
}
impl<'r> Iterator for RefsReferringTo<'r> {
type Item = Result<git2::Reference<'r>>;
fn next(&mut self) -> Option<Self::Item> {
'outer: loop {
if let Some(reference) = self.current_refs.pop() {
return Some(Ok(reference));
}
if self.refs.is_empty() {
return None;
}
for item in &mut self.inner {
match item.chain_err(|| EK::CannotGetCommit) {
Ok(id) => if let Some(new_refs) = self.refs.remove(&id) {
self.current_refs = new_refs;
continue 'outer;
},
Err(err) => return Some(Err(err)),
}
}
return None;
}
}
}
impl<'r> Extend<git2::Reference<'r>> for RefsReferringTo<'r> {
fn extend<I>(&mut self, references: I)
where I: IntoIterator<Item = git2::Reference<'r>>
{
self.current_refs.extend(references);
}
}
pub struct ReferenceDeletingIter<'r, I>
where I: Iterator<Item = git2::Reference<'r>>
{
inner: I
}
impl<'r, I> ReferenceDeletingIter<'r, I>
where I: Iterator<Item = git2::Reference<'r>>
{
pub fn delete_ignoring(self) {
for _ in self {}
}
}
impl<'r, I, J> From<J> for ReferenceDeletingIter<'r, I>
where I: Iterator<Item = git2::Reference<'r>>,
J: IntoIterator<Item = git2::Reference<'r>, IntoIter = I>
{
fn from(items: J) -> Self {
ReferenceDeletingIter { inner: items.into_iter() }
}
}
impl<'r, I> Iterator for ReferenceDeletingIter<'r, I>
where I: Iterator<Item = git2::Reference<'r>>
{
type Item = Error;
fn next(&mut self) -> Option<Self::Item> {
self.inner
.by_ref()
.filter_map(|mut r| r
.delete()
.chain_err(|| EK::CannotDeleteReference(r.name().unwrap_or_default().to_string()))
.err()
)
.next()
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_utils::TestingRepo;
use repository::RepositoryExt;
#[test]
fn referred_refs() {
let mut testing_repo = TestingRepo::new("referred_refs");
let repo = testing_repo.repo();
let sig = git2::Signature::now("Foo Bar", "foo.bar@example.com")
.expect("Could not create signature");
let empty_tree = repo
.empty_tree()
.expect("Could not create empty tree");
let empty_parents: Vec<&git2::Commit> = vec![];
let mut commits = repo.revwalk().expect("Could not create revwalk");
let mut refs_to_watch = Vec::new();
let mut refs_to_report = Vec::new();
{
let commit = repo
.commit(None, &sig, &sig, "Test message 1", &empty_tree, &empty_parents)
.expect("Could not create commit");
let refa = repo
.reference("refs/test/1a", commit, false, "create test ref 1a")
.expect("Could not create reference");
let refb = repo
.reference("refs/test/1b", commit, false, "create test ref 1b")
.expect("Could not create reference");
commits.push(commit).expect("Could not push commit onto revwalk");
refs_to_report.push(refa.name().expect("Could not retrieve name").to_string());
refs_to_report.push(refb.name().expect("Could not retrieve name").to_string());
refs_to_watch.push(refa);
refs_to_watch.push(refb);
}
{
let commit = repo
.commit(None, &sig, &sig, "Test message 2", &empty_tree, &empty_parents)
.expect("Could not create commit");
let refa = repo
.reference("refs/test/2a", commit, false, "create test ref 2a")
.expect("Could not create reference");
repo.reference("refs/test/2b", commit, false, "create test ref 2b")
.expect("Could not create reference");
commits.push(commit).expect("Could not push commit onto revwalk");
refs_to_report.push(refa.name().expect("Could not retrieve name").to_string());
refs_to_watch.push(refa);
}
{
let commit = repo
.commit(None, &sig, &sig, "Test message 3", &empty_tree, &empty_parents)
.expect("Could not create commit");
repo.reference("refs/test/3a", commit, false, "create test ref 3a")
.expect("Could not create reference");
repo.reference("refs/test/3b", commit, false, "create test ref 3b")
.expect("Could not create reference");
commits.push(commit).expect("Could not push commit onto revwalk");
}
{
let commit = repo
.commit(None, &sig, &sig, "Test message 4", &empty_tree, &empty_parents)
.expect("Could not create commit");
let refa = repo
.reference("refs/test/4a", commit, false, "create test ref 4a")
.expect("Could not create reference");
let refb = repo
.reference("refs/test/4b", commit, false, "create test ref 4b")
.expect("Could not create reference");
refs_to_watch.push(refa);
refs_to_watch.push(refb);
}
let mut referred = RefsReferringTo::new(commits);
referred.watch_refs(refs_to_watch).expect("Could not watch refs");
let mut reported: Vec<_> = referred
.map(|item| item
.expect("Error during iterating over refs")
.name()
.expect("Could not retrieve name")
.to_string()
)
.collect();
reported.sort();
refs_to_report.sort();
assert_eq!(reported, refs_to_report);
}
}