use impl_prelude::*;
#[derive(Debug)]
pub struct ThirdParty {
pub name: String,
pub path: String,
pub root: String,
pub utility: String,
}
impl ThirdParty {
pub fn new<N, P, R, U>(name: N, path: P, root: R, utility: U) -> Self
where N: ToString,
P: ToString,
R: ToString,
U: ToString,
{
Self {
name: name.to_string(),
path: path.to_string(),
root: root.to_string(),
utility: utility.to_string(),
}
}
}
enum CheckRefResult {
IsImport,
Rejected(String),
}
impl CheckRefResult {
fn is_import(&self) -> bool {
if let CheckRefResult::IsImport = *self {
true
} else {
false
}
}
fn add_result(self, result: &mut CheckResult) {
if let CheckRefResult::Rejected(err) = self {
result.add_error(err);
}
}
}
impl Check for ThirdParty {
fn name(&self) -> &str {
"third-party"
}
fn check(&self, ctx: &CheckGitContext, commit: &Commit) -> Result<CheckResult> {
let mut result = CheckResult::new();
let mut check_tree = false;
for diff in &commit.diffs {
let name_path = diff.name.as_path();
if !name_path.starts_with(&self.path) {
continue;
}
let check_ref = |sha1: &CommitId| {
let rev_list = ctx.git()
.arg("rev-list")
.arg("--first-parent")
.arg("--max-parents=0")
.arg(sha1.as_str())
.output()
.chain_err(|| "failed to construct rev-list command")?;
if !rev_list.status.success() {
bail!(ErrorKind::Git(format!("failed to get list the root commit for a {} \
third-party change: {}",
self.name,
String::from_utf8_lossy(&rev_list.stderr))));
}
let refs = String::from_utf8_lossy(&rev_list.stdout);
let is_import = self.root == refs.trim();
Ok(if is_import {
CheckRefResult::IsImport
} else {
let msg = format!("commit {} not allowed; the `{}` file is maintained by the \
third party utilities; please use `{}` to update this \
file.",
commit.sha1,
diff.name,
self.utility);
CheckRefResult::Rejected(msg)
})
};
if commit.parents.len() == 2 {
let is_import_res: Result<_> = check_ref(&commit.parents[1]);
let is_import = is_import_res?.is_import();
if is_import {
check_tree = true;
} else {
check_ref(&commit.sha1)?.add_result(&mut result);
}
} else {
check_ref(&commit.sha1)?.add_result(&mut result);
}
}
if check_tree {
let rev_parse = ctx.git()
.arg("rev-parse")
.arg(format!("{}^{{tree}}", commit.parents[1]))
.output()
.chain_err(|| "failed to construct rev-parse command")?;
if !rev_parse.status.success() {
bail!(ErrorKind::Git(format!("failed to parse the expected tree object for the \
{} third-party directory: {}",
self.name,
String::from_utf8_lossy(&rev_parse.stderr))));
}
let expected_tree = String::from_utf8_lossy(&rev_parse.stdout);
let ls_tree = ctx.git()
.arg("ls-tree")
.arg(commit.sha1.as_str())
.arg(&self.path)
.output()
.chain_err(|| "failed to construct ls-tree command")?;
if !ls_tree.status.success() {
bail!(ErrorKind::Git(format!("failed to parse the actual tree object for the \
{} third-party directory: {}",
self.name,
String::from_utf8_lossy(&ls_tree.stderr))));
}
let ls_tree_output = String::from_utf8_lossy(&ls_tree.stdout);
let actual_tree = ls_tree_output.split_whitespace()
.nth(2)
.expect("expected the tree output to have 3 entries");
if actual_tree != expected_tree.trim() {
let msg = format!("commit {} not allowed; the `{}` directory contains changes \
not on the import branch; merge conflicts should not \
happen and indicate that the import directory was \
manually edited at some point. Please find and revert the \
bad edit, apply it to the imported repository (if \
necessary), and then run the import utility.",
commit.sha1,
self.path);
result.add_error(msg);
}
}
Ok(result)
}
}
#[cfg(test)]
mod tests {
use checks::ThirdParty;
use checks::test::*;
const BASE_COMMIT: &str = "26576e49345a141eca310af92737e489c9baac24";
const VALID_UPDATE_TOPIC: &str = "0bd161c8187d4f727a7acc17020711dcc139b166";
const INVALID_UPDATE_TOPIC: &str = "af154fdff05c871125f2db03eccbdde8571d484e";
const EVIL_UPDATE_TOPIC: &str = "add18e5ab9a67303337cb2754c675fb2e0a45a79";
fn make_third_party_check() -> ThirdParty {
ThirdParty::new("check_size",
"check_size",
"d50197ebd7167b0941d34405686164068db0b77b",
"./update.sh")
}
#[test]
fn test_third_party_valid_update() {
let check = make_third_party_check();
let conf = make_check_conf(&check);
let result = test_check_base("test_third_party_valid_update",
VALID_UPDATE_TOPIC,
BASE_COMMIT,
&conf);
test_result_ok(result);
}
#[test]
fn test_third_party_invalid_update() {
let check = make_third_party_check();
let conf = make_check_conf(&check);
let result = test_check_base("test_third_party_invalid_update",
INVALID_UPDATE_TOPIC,
BASE_COMMIT,
&conf);
test_result_errors(result, &[
"commit af154fdff05c871125f2db03eccbdde8571d484e not allowed; the \
`check_size/increased-limit` file is maintained by the third party utilities; please \
use `./update.sh` to update this file.",
]);
}
#[test]
fn test_third_party_invalid_update_evil() {
let check = make_third_party_check();
let conf = make_check_conf(&check);
let result = test_check_base("test_third_party_invalid_update_evil",
EVIL_UPDATE_TOPIC,
BASE_COMMIT,
&conf);
test_result_errors(result, &[
"commit add18e5ab9a67303337cb2754c675fb2e0a45a79 not allowed; the `check_size` \
directory contains changes not on the import branch; merge conflicts should not \
happen and indicate that the import directory was manually edited at some point. \
Please find and revert the bad edit, apply it to the imported repository (if \
necessary), and then run the import utility.",
]);
}
}