use globset::{Glob, GlobMatcher}; use models::repo::{ repo_branch_protection_policy, repo_branch_protection_whitelists, repo_tag_protection_policy, repo_tag_protection_whitelists, }; use sea_orm::{ColumnTrait, EntityTrait, QueryFilter}; use uuid::Uuid; use crate::share::errors::GitError; #[derive(Debug, Default)] pub struct BranchPolicy { patterns: Vec, } impl BranchPolicy { pub fn from_patterns(patterns: impl IntoIterator) -> Result { let mut out = Vec::new(); for p in patterns { let glob = Glob::new(&p).map_err(|e| { GitError::InvalidArgument(format!("invalid branch pattern `{p}`: {e}")) })?; out.push(glob.compile_matcher()); } Ok(Self { patterns: out }) } pub fn is_empty(&self) -> bool { self.patterns.is_empty() } pub fn is_match(&self, name: &str) -> bool { self.patterns.iter().any(|m| m.is_match(name)) } } pub async fn load_branch_policy( db: &dyn export::Database, repo_id: Uuid, ) -> Result { let rows = repo_branch_protection_policy::Entity::find() .filter(repo_branch_protection_policy::Column::RepoId.eq(repo_id)) .all(db.read()) .await?; BranchPolicy::from_patterns(rows.into_iter().map(|r| r.branch_pattern)) } pub async fn load_tag_policy( db: &dyn export::Database, repo_id: Uuid, ) -> Result { let rows = repo_tag_protection_policy::Entity::find() .filter(repo_tag_protection_policy::Column::RepoId.eq(repo_id)) .all(db.read()) .await?; BranchPolicy::from_patterns(rows.into_iter().map(|r| r.name_pattern)) } #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum WhitelistSubject { User(i64), #[allow(dead_code)] Group(Uuid), } #[derive(Debug, Default)] pub struct WhitelistSet { pub push_allowed: Vec, pub merge_allowed: Vec, pub approval_allowed: Vec, } impl WhitelistSet { pub fn push_actor_allowed(&self, subject: WhitelistSubject) -> bool { match subject { WhitelistSubject::User(uid) => self.push_allowed.contains(&uid), WhitelistSubject::Group(_) => false, } } pub fn tag_modifier_allowed(&self, uid: i64) -> bool { self.merge_allowed.contains(&uid) || self.approval_allowed.contains(&uid) } } pub async fn load_branch_whitelists( db: &sea_orm::DatabaseConnection, policy_ids: &[Uuid], ) -> Result { if policy_ids.is_empty() { return Ok(WhitelistSet::default()); } let rows = repo_branch_protection_whitelists::Entity::find() .filter( repo_branch_protection_whitelists::Column::PolicyId.is_in(policy_ids.iter().copied()), ) .all(db) .await?; let mut out = WhitelistSet::default(); for row in rows { let Some(uid) = row.user_id else { continue }; match row.kind { repo_branch_protection_whitelists::WhitelistKind::Push => out.push_allowed.push(uid), repo_branch_protection_whitelists::WhitelistKind::Merge => out.merge_allowed.push(uid), repo_branch_protection_whitelists::WhitelistKind::Approval => { out.approval_allowed.push(uid) } } } Ok(out) } pub async fn load_tag_whitelists( db: &sea_orm::DatabaseConnection, policy_ids: &[Uuid], ) -> Result { if policy_ids.is_empty() { return Ok(WhitelistSet::default()); } let rows = repo_tag_protection_whitelists::Entity::find() .filter(repo_tag_protection_whitelists::Column::PolicyId.is_in(policy_ids.iter().copied())) .all(db) .await?; let mut out = WhitelistSet::default(); for row in rows { out.merge_allowed.push(row.user_id); } Ok(out) } /// 按分支名匹配启用的保护政策(精确名优先,其次最长通配)。 pub async fn match_branch_policies( db: &sea_orm::DatabaseConnection, repo_id: Uuid, branch: &str, ) -> Result, GitError> { let rows = repo_branch_protection_policy::Entity::find() .filter(repo_branch_protection_policy::Column::RepoId.eq(repo_id)) .all(db) .await?; let mut matched: Vec = Vec::new(); for row in rows { if BranchPolicy::from_patterns([row.branch_pattern.clone()])?.is_match(branch) { matched.push(row); } } matched.sort_by_key(|row| { ( row.branch_pattern != branch, -(row.branch_pattern.len() as i32), ) }); let exact: Option = matched .iter() .find(|row| row.branch_pattern == branch) .map(|row| row.branch_pattern.clone()); if let Some(pattern) = exact { matched.retain(|row| row.branch_pattern == pattern); } Ok(matched) } /// 政策对应的白名单集合。 pub async fn branch_policy_whitelists( db: &sea_orm::DatabaseConnection, policies: &[repo_branch_protection_policy::Model], ) -> Result { let ids: Vec = policies.iter().map(|row| row.id).collect(); load_branch_whitelists(db, &ids).await }