extern crate git_workarea;
use self::git_workarea::{CommitId, GitContext, Identity};
extern crate rayon;
use self::rayon::prelude::*;
use super::commit::Commit;
use super::context::CheckGitContext;
use super::check::{BranchCheck, Check, CheckResult};
use super::error::*;
#[derive(Default)]
pub struct GitCheckConfiguration<'a> {
checks: Vec<&'a Check>,
checks_branch: Vec<&'a BranchCheck>,
}
impl<'a> GitCheckConfiguration<'a> {
pub fn new() -> Self {
GitCheckConfiguration {
checks: vec![],
checks_branch: 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 list(&self, ctx: &GitContext, reason: &str, base_branch: &CommitId, topic: &CommitId)
-> Result<Vec<CommitId>> {
let (new_ref, base_ref) = try!(ctx.reserve_refs(&format!("check/{}", reason), &topic));
let update_ref = try!(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 = try!(ctx.git()
.arg("rev-list")
.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(&self, ctx: &GitContext, refs: &[CommitId], topic_owner: &Identity)
-> Result<CheckResult> {
refs.par_iter()
.map(|sha1| {
let workarea = try!(ctx.prepare(&sha1));
let check_ctx = CheckGitContext::new(workarea, topic_owner.clone());
let commit = try!(Commit::new(ctx, sha1));
Ok(self.checks
.par_iter()
.map(|check| {
debug!(target: "git-checks",
"running check {} on commit {}",
check.name(),
commit.sha1);
match check.check(&check_ctx, &commit) {
Ok(check_res) => check_res,
Err(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_short),
true);
res
},
}
})
.reduce(CheckResult::new, CheckResult::combine))
})
.chain({
refs.first()
.map(|head_commit| {
let workarea = try!(ctx.prepare(head_commit));
let check_ctx = CheckGitContext::new(workarea, topic_owner.clone());
Ok(self.checks_branch
.par_iter()
.map(|check| {
debug!(target: "git-checks", "running check {}", check.name());
match check.check(&check_ctx, head_commit) {
Ok(check_res) => check_res,
Err(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
},
}
})
.reduce(CheckResult::new, CheckResult::combine))
})
})
.collect::<Vec<Result<_>>>()
.into_iter()
.collect::<Result<Vec<_>>>()
.map(|check_results| {
check_results.into_iter()
.fold(CheckResult::new(), CheckResult::combine)
})
}
pub fn run(&self, ctx: &GitContext, refs: &[CommitId], topic_owner: &Identity)
-> Result<CheckResult> {
self._run(ctx, refs, topic_owner)
}
pub fn run_topic<R>(&self, ctx: &GitContext, reason: R, base_branch: &CommitId,
topic: &CommitId, owner: &Identity)
-> Result<CheckResult>
where R: AsRef<str>,
{
let refs = try!(self.list(ctx, reason.as_ref(), base_branch, topic));
self._run(ctx, &refs, owner)
}
}