//! policy: external protection-policy callback for refs and tags. //! //! The default policy protects common integration branches. Callers that host //! this library can inject their own `PolicyProvider` via //! [`GitRepository::with_policy`](crate::GitRepository::with_policy) to enforce //! real repository-specific protection rules. use std::sync::Arc; /// A protection policy consulted before mutating refs/tags. /// /// Implementations must be cheap, pure, and side-effect free. They are called /// on every ref/tag mutation to decide whether the operation is allowed. pub trait PolicyProvider: Send + Sync { /// Whether the given branch (short name, e.g. `main`) is protected. fn is_branch_protected(&self, branch: &str) -> bool; /// Whether the given tag (short name) is protected. fn is_tag_protected(&self, tag: &str) -> bool; } /// Default policy: protects common integration branches, nothing else. /// /// This is a safe default for a git-hosting backend: it blocks force-push and /// deletion of `main`/`master`/`develop` and `release/*` tags, while allowing /// anything else. Callers can replace it with their own provider. #[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/") } } /// A policy that protects nothing (used by tests / callers that opt out). #[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 } } /// Convenience alias for the boxed policy held by a repository. pub type Policy = Arc; /// Build an [`Arc`] from a concrete provider. 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")); } }