use std::sync::Arc; /// on every ref/tag mutation to decide whether the operation is allowed. pub trait PolicyProvider: Send + Sync { fn is_branch_protected(&self, branch: &str) -> bool; fn is_tag_protected(&self, tag: &str) -> bool; } /// deletion of `main`/`master`/`develop` and `release/*` tags, while allowing #[derive(Debug, Clone, Default)] pub struct DefaultPolicy; impl DefaultPolicy { fn is_integration_branch(branch: &str) -> bool { matches!(branch, "main" | "master" | "develop") } } impl PolicyProvider for DefaultPolicy { fn is_branch_protected(&self, branch: &str) -> bool { Self::is_integration_branch(branch) } fn is_tag_protected(&self, tag: &str) -> bool { tag.starts_with("release/") } } #[derive(Debug, Clone, Default)] pub struct NoProtection; impl PolicyProvider for NoProtection { fn is_branch_protected(&self, _branch: &str) -> bool { false } fn is_tag_protected(&self, _tag: &str) -> bool { false } } pub type Policy = Arc; pub fn arc(provider: P) -> Policy { Arc::new(provider) } #[cfg(test)] mod tests { use super::*; #[test] fn default_policy_protects_integration_branches() { let p = DefaultPolicy; assert!(p.is_branch_protected("main")); assert!(p.is_branch_protected("master")); assert!(p.is_branch_protected("develop")); assert!(!p.is_branch_protected("feature")); assert!(p.is_tag_protected("release/v1.0")); assert!(!p.is_tag_protected("v1.0")); } #[test] fn no_protection_protects_nothing() { let p = NoProtection; assert!(!p.is_branch_protected("main")); assert!(!p.is_tag_protected("release/v1.0")); } }