Your browser sucks, JavaScript disabled! Gunnar's console needs JavaScript and WebAssembly. Git over SSH on port 2222 does not.

nordisk/gunnar

crates/gunnar-policy/src/gates/refname.rs

//! Gate 2 decides which reference names may exist.
//!
//! The cheapest gate and the one most likely to be written blind, because a
//! test that pushes `refs/heads/main` through an allowlist containing
//! `refs/heads/*` passes whether or not the matcher does anything at all. The
//! tests behind it therefore always assert a *refused* name alongside an accepted
//! one, and the `**`-versus-`*` distinction is pinned separately in
//! [`crate::pattern`].

use crate::config::RefNamePolicy;
use crate::context::PushContext;
use crate::error::Result;
use crate::pattern;
use crate::{Gate, Verdict};

/// Refuses reference names the namespace has not sanctioned.
#[derive(Clone, Debug)]
pub struct RefNameGate {
    policy: RefNamePolicy,
}

impl RefNameGate {
    /// A gate over `policy`.
    pub fn new(policy: RefNamePolicy) -> Self {
        RefNameGate { policy }
    }

    /// The policy this gate enforces.
    pub fn policy(&self) -> &RefNamePolicy {
        &self.policy
    }
}

impl Gate for RefNameGate {
    fn name(&self) -> &str {
        "ref-name"
    }

    fn check(&self, ctx: &PushContext<'_>) -> Result<Verdict> {
        for update in ctx.updates {
            // A delete is judged on the name too: a reference that should never
            // have existed is still a reference somebody may not touch, and
            // exempting deletes here would make the denylist a suggestion.
            if let Some(denied) = pattern::first_match(&self.policy.deny, &update.name) {
                return Ok(Verdict::reject_ref(
                    update.name.clone(),
                    format!(
                        "reference name {} is denied by pattern {denied}",
                        update.name
                    ),
                ));
            }
            if !self.policy.allow.is_empty()
                && !pattern::any_matches(&self.policy.allow, &update.name)
            {
                let allowed = self
                    .policy
                    .allow
                    .iter()
                    .map(|p| p.as_str())
                    .collect::<Vec<_>>()
                    .join(", ");
                return Ok(Verdict::reject_ref(
                    update.name.clone(),
                    format!(
                        "reference name {} matches no allowed pattern ({allowed})",
                        update.name
                    ),
                ));
            }
        }
        Ok(Verdict::Allow)
    }
}