c9e91cd
//! 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<dyn PolicyProvider>;
/// Build an [`Arc<dyn PolicyProvider>`] from a concrete provider.
pub fn arc<P: PolicyProvider + 'static>(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"));
}
}