use crates::git_workarea::{CommitId, GitContext, Identity};
use crates::rayon::prelude::*;
use check::{BranchCheck, Check, CheckResult, TopicCheck};
use commit::{Commit, Topic};
use context::CheckGitContext;
use error::*;
use std::fmt::{self, Debug};
use std::iter;
use std::slice;
#[derive(Default)]
pub struct GitCheckConfiguration<'a> {
checks: Vec<&'a Check>,
checks_branch: Vec<&'a BranchCheck>,
checks_topic: Vec<&'a TopicCheck>,
}
#[derive(Debug)]
pub struct TopicCheckResult {
commit_results: Vec<(CommitId, CheckResult)>,
topic_result: CheckResult,
}
impl TopicCheckResult {
pub fn commit_results(&self) -> slice::Iter<(CommitId, CheckResult)> {
self.commit_results.iter()
}
pub fn topic_result(&self) -> &CheckResult {
&self.topic_result
}
}
impl From<TopicCheckResult> for CheckResult {
fn from(res: TopicCheckResult) -> Self {
res.commit_results
.into_iter()
.map(|(_, result)| result)
.chain(iter::once(res.topic_result))
.fold(Self::new(), Self::combine)
}
}
impl<'a> GitCheckConfiguration<'a> {
pub fn new() -> Self {
GitCheckConfiguration {
checks: vec![],
checks_branch: vec![],
checks_topic: vec![],
}
}
pub fn add_check(&mut self, check: &'a Check) -> &mut Self {
self.checks.push(check);
self
}
pub fn add_branch_check(&mut self, check: &'a BranchCheck) -> &mut Self {
self.checks_branch.push(check);
self
}
pub fn add_topic_check(&mut self, check: &'a TopicCheck) -> &mut Self {
self.checks_topic.push(check);
self
}
fn list(&self, ctx: &GitContext, reason: &str, base_branch: &CommitId, topic: &CommitId)
-> Result<Vec<CommitId>> {
let (new_ref, base_ref) = ctx.reserve_refs(&format!("check/{}", reason), topic)?;
let update_ref = ctx.git()
.arg("update-ref")
.arg("-m").arg(reason)
.arg(&base_ref)
.arg(base_branch.as_str())
.output()
.chain_err(|| "failed to construct update-ref command")?;
if !update_ref.status.success() {
bail!(ErrorKind::Git(format!("failed to update the {} ref: {}",
base_ref,
String::from_utf8_lossy(&update_ref.stderr))));
}
let rev_list = ctx.git()
.arg("rev-list")
.arg("--reverse")
.arg("--topo-order")
.arg(&new_ref)
.arg(&format!("^{}", base_ref))
.output()
.chain_err(|| "failed to construct rev-list command")?;
if !rev_list.status.success() {
bail!(ErrorKind::Git(format!("failed to list all branch refs: {}",
String::from_utf8_lossy(&rev_list.stderr))));
}
let refs = String::from_utf8_lossy(&rev_list.stdout);
Ok(refs.lines().map(CommitId::new).collect())
}
fn run_check(ctx: &CheckGitContext, check: &Check, commit: &Commit) -> CheckResult {
debug!(target: "git-checks",
"running check {} on commit {}",
check.name(),
commit.sha1);
check.check(ctx, commit)
.unwrap_or_else(|err| {
error!(target: "git-checks",
"check {} failed on commit {}: {:?}",
check.name(),
commit.sha1,
err);
let mut res = CheckResult::new();
res.add_alert(format!("failed to run the {} check on commit {}",
check.name(),
commit.sha1),
true);
res
})
}
fn run_branch_check(ctx: &CheckGitContext, check: &BranchCheck, commit: &CommitId)
-> CheckResult {
debug!(target: "git-checks", "running check {}", check.name());
check.check(ctx, commit)
.unwrap_or_else(|err| {
error!(target: "git-checks",
"branch check {}: {:?}",
check.name(),
err);
let mut res = CheckResult::new();
res.add_alert(format!("failed to run the {} branch check", check.name()),
true);
res
})
}
fn run_topic_check(ctx: &CheckGitContext, check: &TopicCheck, topic: &Topic)
-> CheckResult {
debug!(target: "git-checks", "running check {}", check.name());
check.check(ctx, topic)
.unwrap_or_else(|err| {
error!(target: "git-checks",
"topic check {}: {:?}",
check.name(),
err);
let mut res = CheckResult::new();
res.add_alert(format!("failed to run the {} topic check", check.name()),
true);
res
})
}
fn run_topic_impl(&self, ctx: &GitContext, base: &CommitId, refs: Vec<CommitId>,
owner: &Identity)
-> Result<TopicCheckResult> {
let topic_result = refs.last()
.map_or_else(|| Ok(CheckResult::new()) as Result<_>, |head_commit| {
if self.checks_branch.is_empty() && self.checks_topic.is_empty() {
return Ok(CheckResult::new());
}
let workarea = ctx.prepare(head_commit)?;
let check_ctx = CheckGitContext::new(workarea, owner.clone());
let topic = Topic::new(ctx, base, head_commit)?;
Ok(self.checks_branch
.par_iter()
.map(|&check| Self::run_branch_check(&check_ctx, check, head_commit))
.chain(self.checks_topic
.par_iter()
.map(|&check| Self::run_topic_check(&check_ctx, check, &topic)))
.reduce(CheckResult::new, CheckResult::combine))
})?;
let commit_results = refs.into_par_iter()
.map(|sha1| {
self.run_commit(ctx, &sha1, owner)
.map(|result| (sha1, result))
})
.collect::<Vec<Result<_>>>()
.into_iter()
.collect::<Result<Vec<_>>>()?;
Ok(TopicCheckResult {
commit_results: commit_results,
topic_result: topic_result,
})
}
pub fn run_commit(&self, ctx: &GitContext, commit: &CommitId, owner: &Identity)
-> Result<CheckResult> {
if self.checks.is_empty() {
return Ok(CheckResult::new());
}
let workarea = ctx.prepare(commit)?;
let check_ctx = CheckGitContext::new(workarea, owner.clone());
let commit = Commit::new(ctx, commit)?;
Ok(self.checks
.par_iter()
.map(|&check| Self::run_check(&check_ctx, check, &commit))
.reduce(CheckResult::new, CheckResult::combine))
}
pub fn run_topic<R>(&self, ctx: &GitContext, reason: R, base_branch: &CommitId,
topic: &CommitId, owner: &Identity)
-> Result<TopicCheckResult>
where R: AsRef<str>,
{
let refs = self.list(ctx, reason.as_ref(), base_branch, topic)?;
self.run_topic_impl(ctx, base_branch, refs, owner)
}
}
impl<'a> Debug for GitCheckConfiguration<'a> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f,
"GitCheckConfiguration {{ {} commit checks, {} branch checks }}",
self.checks.len(),
self.checks_branch.len())
}
}