use super::super::*;
#[derive(Debug, Default, Clone, Copy)]
pub struct SubmoduleAvailable {
require_first_parent: bool,
}
impl SubmoduleAvailable {
pub fn new() -> Self {
SubmoduleAvailable {
require_first_parent: false,
}
}
pub fn require_first_parent(&mut self, require: bool) -> &mut Self {
self.require_first_parent = require;
self
}
}
impl Check for SubmoduleAvailable {
fn name(&self) -> &str {
"submodule-available"
}
fn check(&self, ctx: &CheckGitContext, commit: &Commit) -> Result<CheckResult> {
let mut result = CheckResult::new();
for diff in &commit.diffs {
if diff.new_mode != "160000" {
continue;
}
if let StatusChange::Deleted = diff.status {
continue;
}
let submodule_ctx = SubmoduleContext::new(ctx, diff.name.as_ref());
if submodule_ctx.is_none() {
result.add_alert(format!("submodule at `{}` is not configured.", diff.name),
false);
continue;
}
let submodule_ctx = submodule_ctx.unwrap();
let submodule_commit = &diff.new_blob;
let cat_file = try!(submodule_ctx.context
.git()
.arg("cat-file")
.arg("-t")
.arg(submodule_commit.as_str())
.output()
.chain_err(|| "failed to construct cat-file command"));
let object_type = String::from_utf8_lossy(&cat_file.stdout);
if !cat_file.status.success() || object_type.trim() != "commit" {
result.add_error(format!("commit {} references an unreachable commit {} at \
`{}`; please make the commit available in the {} \
repository on the `{}` branch first.",
commit.sha1_short,
submodule_commit,
submodule_ctx.path,
submodule_ctx.url,
submodule_ctx.branch));
continue;
}
let merge_base = try!(submodule_ctx.context
.git()
.arg("merge-base")
.arg(submodule_commit.as_str())
.arg(submodule_ctx.branch)
.output()
.chain_err(|| "failed to construct merge-base command"));
if !merge_base.status.success() {
bail!(ErrorKind::Git(format!("failed to get the merge base for the {} \
submodule: {}",
diff.name,
String::from_utf8_lossy(&merge_base.stderr))));
}
let base = String::from_utf8_lossy(&merge_base.stdout);
if base.trim() != submodule_commit.as_str() {
result.add_error(format!("commit {} references the commit {} at `{}`, but it is \
not available on the tracked branch `{}`; please make \
the commit available from the `{}` branch first.",
commit.sha1_short,
submodule_commit,
submodule_ctx.path,
submodule_ctx.branch,
submodule_ctx.branch));
continue;
}
if self.require_first_parent {
let refs = try!(submodule_ctx.context
.git()
.arg("rev-list")
.arg("--first-parent") .arg("--reverse") .arg(submodule_ctx.branch)
.arg(format!("^{}~", submodule_commit))
.output()
.chain_err(|| "failed to construct rev-list command"));
if !refs.status.success() {
bail!(ErrorKind::Git(format!("failed to get list the first parent history \
for the {} submodule: {}",
diff.name,
String::from_utf8_lossy(&refs.stderr))));
}
let refs = String::from_utf8_lossy(&refs.stdout);
if !refs.lines().any(|rev| rev == submodule_commit.as_str()) {
result.add_error(format!("commit {} references the commit {} at `{}`, but \
it is not available as a first-parent of the \
tracked branch `{}`; please choose the commit \
where it was merged into the `{}` branch.",
commit.sha1_short,
submodule_commit,
submodule_ctx.path,
submodule_ctx.branch,
submodule_ctx.branch));
continue;
}
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use super::SubmoduleAvailable;
use super::super::test::*;
static BASE_COMMIT: &'static str = "fe90ee22ae3ce4b4dc41f8d0876e59355ff1e21c";
static MOVE_TOPIC: &'static str = "2088079e35503be3be41dbdca55080ced95614e1";
static MOVE_NOT_FIRST_PARENT_TOPIC: &'static str = "eb4df16a8a38f6ca30b6e67cfbca0672156b54d2";
static UNAVAILABLE_TOPIC: &'static str = "1b9275caca1557611df19d1dfea687c3ef302eef";
static NOT_ANCESTOR_TOPIC: &'static str = "07fb2ca9c1c8c0ddfcf921e762688ffcd476bc09";
#[test]
fn test_submodule_unconfigured() {
let check = SubmoduleAvailable::new();
let mut conf = GitCheckConfiguration::new();
conf.add_check(&check);
let result = test_check("test_submodule_unconfigured", BASE_COMMIT, &conf);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 1);
assert_eq!(result.alerts()[0],
"submodule at `submodule` is not configured.");
assert_eq!(result.errors().len(), 0);
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), true);
}
#[test]
fn test_submodule_move() {
let check = SubmoduleAvailable::new();
let mut conf = GitCheckConfiguration::new();
conf.add_check(&check);
let result = test_check_submodule("test_submodule_move", MOVE_TOPIC, &conf);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 0);
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), true);
}
#[test]
fn test_submodule_move_not_first_parent() {
let check = SubmoduleAvailable::new();
let mut conf = GitCheckConfiguration::new();
conf.add_check(&check);
let result = test_check_submodule("test_submodule_move_not_first_parent",
MOVE_NOT_FIRST_PARENT_TOPIC,
&conf);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 0);
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), true);
}
#[test]
fn test_submodule_move_not_first_parent_reject() {
let mut check = SubmoduleAvailable::new();
check.require_first_parent(true);
let mut conf = GitCheckConfiguration::new();
conf.add_check(&check);
let result = test_check_submodule("test_submodule_move_not_first_parent_reject",
MOVE_NOT_FIRST_PARENT_TOPIC,
&conf);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 1);
assert_eq!(result.errors()[0],
"commit eb4df16 references the commit \
c2bd427807b40b1715b8d1441fe92f50e8ad1769 at `submodule`, but it is not \
available as a first-parent of the tracked branch `master`; please choose \
the commit where it was merged into the `master` branch.");
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), false);
}
#[test]
fn test_submodule_unavailable() {
let check = SubmoduleAvailable::new();
let mut conf = GitCheckConfiguration::new();
conf.add_check(&check);
let result = test_check_submodule("test_submodule_unavailable", UNAVAILABLE_TOPIC, &conf);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 1);
assert_eq!(result.errors()[0],
"commit 1b9275c references an unreachable commit \
4b029c2e0f186d681caa071fa4dd7eb1f0f033f6 at `submodule`; please make the \
commit available in the https://gitlab.kitware.com/utils/test-repo.git \
repository on the `master` branch first.");
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), false);
}
#[test]
fn test_submodule_not_ancestor() {
let check = SubmoduleAvailable::new();
let mut conf = GitCheckConfiguration::new();
conf.add_check(&check);
let result = test_check_submodule("test_submodule_not_ancestor", NOT_ANCESTOR_TOPIC, &conf);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 1);
assert_eq!(result.errors()[0],
"commit 07fb2ca references the commit \
bd89a556b6ab6f378a776713439abbc1c1f15b6d at `submodule`, but it is not \
available on the tracked branch `master`; please make the commit available \
from the `master` branch first.");
assert_eq!(result.allowed(), false);
assert_eq!(result.pass(), false);
}
}