use derive_builder::Builder;
use git_checks_core::impl_prelude::*;
#[derive(Builder, Debug, Default, Clone, Copy)]
#[builder(field(private))]
pub struct SubmoduleWatch {
#[builder(default = "false")]
reject_additions: bool,
#[builder(default = "false")]
reject_removals: bool,
}
impl SubmoduleWatch {
pub fn builder() -> SubmoduleWatchBuilder {
SubmoduleWatchBuilder::default()
}
}
impl Check for SubmoduleWatch {
fn name(&self) -> &str {
"submodule-watch"
}
fn check(&self, ctx: &CheckGitContext, commit: &Commit) -> Result<CheckResult, Box<dyn Error>> {
let mut result = CheckResult::new();
for diff in &commit.diffs {
let added = diff.new_mode == "160000";
let removed = diff.old_mode == "160000";
if !added && !removed {
continue;
}
let is_configured = SubmoduleContext::new(ctx, diff.name.as_ref()).is_some();
if added && removed {
if !is_configured {
result.add_warning(format!(
"commit {} modifies an unconfigured submodule at `{}`.",
commit.sha1, diff.name,
));
result.make_temporary();
}
continue;
}
if added && !is_configured {
if self.reject_additions {
result.add_error(format!(
"commit {} adds a submodule at `{}` which is not allowed.",
commit.sha1, diff.name,
));
} else {
result.add_alert(
format!(
"commit {} adds a submodule at `{}`.",
commit.sha1, diff.name,
),
false,
);
}
result.make_temporary();
}
if removed {
if self.reject_removals {
result.add_error(format!(
"commit {} removes the submodule at `{}` which is not allowed.",
commit.sha1, diff.name,
));
} else {
result.add_alert(
format!(
"commit {} removes the submodule at `{}`.",
commit.sha1, diff.name,
),
false,
);
}
}
}
Ok(result)
}
}
#[cfg(feature = "config")]
pub(crate) mod config {
use git_checks_config::{register_checks, CommitCheckConfig, IntoCheck};
use serde::Deserialize;
#[cfg(test)]
use serde_json::json;
use crate::SubmoduleWatch;
#[derive(Deserialize, Debug)]
pub struct SubmoduleWatchConfig {
#[serde(default)]
reject_additions: Option<bool>,
#[serde(default)]
reject_removals: Option<bool>,
}
impl IntoCheck for SubmoduleWatchConfig {
type Check = SubmoduleWatch;
fn into_check(self) -> Self::Check {
let mut builder = SubmoduleWatch::builder();
if let Some(reject_additions) = self.reject_additions {
builder.reject_additions(reject_additions);
}
if let Some(reject_removals) = self.reject_removals {
builder.reject_removals(reject_removals);
}
builder
.build()
.expect("configuration mismatch for `SubmoduleWatch`")
}
}
register_checks! {
SubmoduleWatchConfig {
"submodule_watch" => CommitCheckConfig,
},
}
#[test]
fn test_submodule_watch_config_empty() {
let json = json!({});
let check: SubmoduleWatchConfig = serde_json::from_value(json).unwrap();
assert_eq!(check.reject_additions, None);
assert_eq!(check.reject_removals, None);
let check = check.into_check();
assert!(!check.reject_additions);
assert!(!check.reject_removals);
}
#[test]
fn test_submodule_watch_config_all_fields() {
let json = json!({
"reject_additions": true,
"reject_removals": true,
});
let check: SubmoduleWatchConfig = serde_json::from_value(json).unwrap();
assert_eq!(check.reject_additions, Some(true));
assert_eq!(check.reject_removals, Some(true));
let check = check.into_check();
assert!(check.reject_additions);
assert!(check.reject_removals);
}
}
#[cfg(test)]
mod tests {
use git_checks_core::Check;
use crate::test::*;
use crate::SubmoduleWatch;
const ADD_SUBMODULE_TOPIC: &str = "fe90ee22ae3ce4b4dc41f8d0876e59355ff1e21c";
const REMOVE_SUBMODULE_TOPIC: &str = "336dbaa31d512033fe77eaba7f92ebfecbd17a39";
const REMOVE_SUBMODULE_AS_FILE: &str = "24573935ac8f352893022e454d03a6450a9e5fe5";
const ADD_SUBMODULE_FROM_FILE: &str = "dab435c23d367c6288540cd97017a0dcd3ac042d";
const MOVE_SUBMODULE: &str = "2088079e35503be3be41dbdca55080ced95614e1";
#[test]
fn test_submodule_watch_builder_default() {
assert!(SubmoduleWatch::builder().build().is_ok());
}
#[test]
fn test_submodule_watch_name_commit() {
let check = SubmoduleWatch::default();
assert_eq!(Check::name(&check), "submodule-watch");
}
#[test]
fn test_submodule_watch_add() {
let check = SubmoduleWatch::default();
let result = run_check("test_submodule_watch_add", ADD_SUBMODULE_TOPIC, check);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 1);
assert_eq!(
result.alerts()[0],
"commit fe90ee22ae3ce4b4dc41f8d0876e59355ff1e21c adds a submodule at `submodule`.",
);
assert_eq!(result.errors().len(), 0);
assert!(result.temporary());
assert!(!result.allowed());
assert!(result.pass());
}
#[test]
fn test_submodule_watch_add_from_file() {
let check = SubmoduleWatch::default();
let conf = make_check_conf(&check);
let result = test_check_base(
"test_submodule_watch_add_from_file",
ADD_SUBMODULE_FROM_FILE,
REMOVE_SUBMODULE_AS_FILE,
&conf,
);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 1);
assert_eq!(
result.alerts()[0],
"commit dab435c23d367c6288540cd97017a0dcd3ac042d adds a submodule at `submodule`.",
);
assert_eq!(result.errors().len(), 0);
assert!(result.temporary());
assert!(!result.allowed());
assert!(result.pass());
}
#[test]
fn test_submodule_watch_add_reject() {
let check = SubmoduleWatch::builder()
.reject_additions(true)
.build()
.unwrap();
let result = run_check(
"test_submodule_watch_add_reject",
ADD_SUBMODULE_TOPIC,
check,
);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 1);
assert_eq!(
result.errors()[0],
"commit fe90ee22ae3ce4b4dc41f8d0876e59355ff1e21c adds a submodule at `submodule` \
which is not allowed.",
);
assert!(result.temporary());
assert!(!result.allowed());
assert!(!result.pass());
}
#[test]
fn test_submodule_watch_add_from_file_reject() {
let check = SubmoduleWatch::builder()
.reject_additions(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_base(
"test_submodule_watch_add_from_file_reject",
ADD_SUBMODULE_FROM_FILE,
REMOVE_SUBMODULE_AS_FILE,
&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 dab435c23d367c6288540cd97017a0dcd3ac042d adds a submodule at `submodule` \
which is not allowed.",
);
assert!(result.temporary());
assert!(!result.allowed());
assert!(!result.pass());
}
#[test]
fn test_submodule_watch_add_configured() {
let check = SubmoduleWatch::default();
let conf = make_check_conf(&check);
let result = test_check_submodule_configure(
"test_submodule_watch_add_configured",
ADD_SUBMODULE_TOPIC,
&conf,
"submodule",
);
test_result_ok(result);
}
#[test]
fn test_submodule_watch_add_from_file_configured() {
let check = SubmoduleWatch::default();
let conf = make_check_conf(&check);
let result = test_check_submodule_base_configure(
"test_submodule_watch_add_from_file_configured",
ADD_SUBMODULE_FROM_FILE,
REMOVE_SUBMODULE_AS_FILE,
&conf,
"submodule",
);
test_result_ok(result);
}
#[test]
fn test_submodule_watch_add_configured_reject() {
let check = SubmoduleWatch::builder()
.reject_additions(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_submodule_configure(
"test_submodule_watch_add_configured_reject",
ADD_SUBMODULE_TOPIC,
&conf,
"submodule",
);
test_result_ok(result);
}
#[test]
fn test_submodule_watch_add_from_file_configured_reject() {
let check = SubmoduleWatch::builder()
.reject_additions(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_submodule_base_configure(
"test_submodule_watch_add_from_file_configured_reject",
ADD_SUBMODULE_FROM_FILE,
REMOVE_SUBMODULE_AS_FILE,
&conf,
"submodule",
);
test_result_ok(result);
}
#[test]
fn test_submodule_watch_remove() {
let check = SubmoduleWatch::default();
let conf = make_check_conf(&check);
let result = test_check_submodule_base(
"test_submodule_watch_remove",
REMOVE_SUBMODULE_TOPIC,
ADD_SUBMODULE_TOPIC,
&conf,
);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 1);
assert_eq!(
result.alerts()[0],
"commit 336dbaa31d512033fe77eaba7f92ebfecbd17a39 removes the submodule at `submodule`.",
);
assert_eq!(result.errors().len(), 0);
assert!(!result.temporary());
assert!(!result.allowed());
assert!(result.pass());
}
#[test]
fn test_submodule_watch_remove_as_file() {
let check = SubmoduleWatch::default();
let conf = make_check_conf(&check);
let result = test_check_submodule_base(
"test_submodule_watch_remove_as_file",
REMOVE_SUBMODULE_AS_FILE,
ADD_SUBMODULE_TOPIC,
&conf,
);
assert_eq!(result.warnings().len(), 0);
assert_eq!(result.alerts().len(), 1);
assert_eq!(
result.alerts()[0],
"commit 24573935ac8f352893022e454d03a6450a9e5fe5 removes the submodule at `submodule`.",
);
assert_eq!(result.errors().len(), 0);
assert!(!result.temporary());
assert!(!result.allowed());
assert!(result.pass());
}
#[test]
fn test_submodule_watch_remove_reject() {
let check = SubmoduleWatch::builder()
.reject_removals(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_submodule_base(
"test_submodule_watch_remove_reject",
REMOVE_SUBMODULE_TOPIC,
ADD_SUBMODULE_TOPIC,
&conf,
);
test_result_errors(result, &[
"commit 336dbaa31d512033fe77eaba7f92ebfecbd17a39 removes the submodule at `submodule` \
which is not allowed.",
]);
}
#[test]
fn test_submodule_watch_remove_as_file_reject() {
let check = SubmoduleWatch::builder()
.reject_removals(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_submodule_base(
"test_submodule_watch_remove_as_file_reject",
REMOVE_SUBMODULE_AS_FILE,
ADD_SUBMODULE_TOPIC,
&conf,
);
test_result_errors(result, &[
"commit 24573935ac8f352893022e454d03a6450a9e5fe5 removes the submodule at `submodule` \
which is not allowed.",
]);
}
#[test]
fn test_submodule_watch_modified() {
let check = SubmoduleWatch::builder()
.reject_removals(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_base(
"test_submodule_watch_modified",
MOVE_SUBMODULE,
ADD_SUBMODULE_TOPIC,
&conf,
);
assert_eq!(result.warnings().len(), 1);
assert_eq!(
result.warnings()[0],
"commit 2088079e35503be3be41dbdca55080ced95614e1 modifies an unconfigured submodule \
at `submodule`.",
);
assert_eq!(result.alerts().len(), 0);
assert_eq!(result.errors().len(), 0);
assert!(result.temporary());
assert!(!result.allowed());
assert!(result.pass());
}
#[test]
fn test_submodule_watch_configure_modified() {
let check = SubmoduleWatch::builder()
.reject_removals(true)
.build()
.unwrap();
let conf = make_check_conf(&check);
let result = test_check_submodule_base_configure(
"test_submodule_watch_configure_modified",
MOVE_SUBMODULE,
ADD_SUBMODULE_TOPIC,
&conf,
"submodule",
);
test_result_ok(result);
}
}