use git2::{self, Reference};
use issue::{Issue, IssueRefType};
use iter::{self, RefsReferringTo};
use utils::ResultIterExt;
use error::*;
use error::ErrorKind as EK;
pub type ReferenceCollector<'r> = iter::ReferenceDeletingIter<
'r,
<Vec<Reference<'r>> as IntoIterator>::IntoIter
>;
pub enum ReferenceCollectionSpec {
Never,
BackedByRemoteHead,
}
pub struct CollectableRefs<'r>
{
repo: &'r git2::Repository,
consider_remote_refs: bool,
collect_heads: ReferenceCollectionSpec,
}
impl<'r> CollectableRefs<'r>
{
pub fn new(repo: &'r git2::Repository) -> Self
{
CollectableRefs {
repo: repo,
consider_remote_refs: false,
collect_heads: ReferenceCollectionSpec::Never,
}
}
pub fn consider_remote_refs(mut self, option: bool) -> Self {
self.consider_remote_refs = option;
self
}
pub fn collect_heads(mut self, condition: ReferenceCollectionSpec) -> Self {
self.collect_heads = condition;
self
}
pub fn for_issue(&self, issue: &Issue<'r>) -> Result<RefsReferringTo<'r>> {
let mut retval = {
let messages = self
.repo
.revwalk()
.chain_err(|| EK::CannotConstructRevwalk)?;
RefsReferringTo::new(messages)
};
if let Some(local_head) = issue.local_head().ok() {
retval.push(
local_head
.peel(git2::ObjectType::Commit)
.chain_err(|| EK::CannotGetCommit)?
.id()
)?;
let mut head_history = self
.repo
.revwalk()
.chain_err(|| EK::CannotConstructRevwalk)?;
match self.collect_heads {
ReferenceCollectionSpec::Never => {},
ReferenceCollectionSpec::BackedByRemoteHead => {
for item in issue.remote_refs(IssueRefType::Head)? {
head_history.push(
item?
.peel(git2::ObjectType::Commit)
.chain_err(|| EK::CannotGetCommit)?
.id()
)?;
}
},
};
let mut referring_refs = iter::RefsReferringTo::new(head_history);
referring_refs.watch_ref(local_head)?;
referring_refs.collect_result_into(&mut retval)?;
}
for item in issue.local_refs(IssueRefType::Leaf)? {
let leaf = item?;
Self::push_ref_parents(&mut retval, &leaf)?;
retval.watch_ref(leaf)?;
}
if self.consider_remote_refs {
for item in issue.remote_refs(IssueRefType::Any)? {
retval.push(item?
.peel(git2::ObjectType::Commit)
.chain_err(|| EK::CannotGetCommit)?
.id()
)?;
}
}
Ok(retval)
}
fn push_ref_parents<'a>(target: &mut RefsReferringTo, reference: &'a Reference<'a>) -> Result<()>
{
let referred_commit = reference
.peel(git2::ObjectType::Commit)
.chain_err(|| EK::CannotGetCommit)?
.into_commit()
.map_err(|o| Error::from_kind(EK::CannotGetCommitForRev(o.id().to_string())))?;
for parent in referred_commit.parent_ids() {
target.push(parent)?;
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use test_utils::TestingRepo;
use repository::RepositoryExt;
#[test]
fn collectable_leaves() {
let mut testing_repo = TestingRepo::new("collectable_leaves");
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 mut refs_to_collect = Vec::new();
let mut issues = Vec::new();
{
let issue = repo
.create_issue(&sig, &sig, "Test message 1", &empty_tree, vec![])
.expect("Could not create issue");
let initial_message = issue
.initial_message()
.expect("Could not retrieve initial message");
issue.add_message(&sig, &sig, "Test message 2", &empty_tree, vec![&initial_message])
.expect("Could not add message");
}
{
let issue = repo
.create_issue(&sig, &sig, "Test message 3", &empty_tree, vec![])
.expect("Could not create issue");
let initial_message = issue
.initial_message()
.expect("Could not retrieve initial message");
let message = issue
.add_message(&sig, &sig, "Test message 4", &empty_tree, vec![&initial_message])
.expect("Could not add message");
issue.update_head(message.id(), true).expect("Could not update head");
issues.push(issue);
refs_to_collect.push(message.id());
}
{
let issue = repo
.create_issue(&sig, &sig, "Test message 5", &empty_tree, vec![])
.expect("Could not create issue");
let initial_message = issue
.initial_message()
.expect("Could not retrieve initial message");
let message1 = issue
.add_message(&sig, &sig, "Test message 6", &empty_tree, vec![&initial_message])
.expect("Could not add message");
issue
.add_message(&sig, &sig, "Test message 7", &empty_tree, vec![&message1])
.expect("Could not add message");
issues.push(issue);
refs_to_collect.push(message1.id());
}
refs_to_collect.sort();
let collectable = CollectableRefs::new(repo).collect_heads(ReferenceCollectionSpec::BackedByRemoteHead);
let mut collected: Vec<_> = issues
.iter()
.flat_map(|i| collectable.for_issue(i).expect("Error during discovery of collectable refs"))
.collect::<Result<Vec<_>>>()
.expect("Error during collection")
.into_iter()
.map(|r| r.peel(git2::ObjectType::Commit).expect("Could not peel ref").id())
.collect();
collected.sort();
assert_eq!(refs_to_collect, collected);
}
}