Skip to content
Clone
e1df092
170 lines 5.2 KB Rust
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<GlobMatcher>,
}
impl BranchPolicy {
pub fn from_patterns(patterns: impl IntoIterator<Item = String>) -> Result<Self, GitError> {
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: &export::AppDatabase,
repo_id: Uuid,
) -> Result<BranchPolicy, GitError> {
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: &export::AppDatabase,
repo_id: Uuid,
) -> Result<BranchPolicy, GitError> {
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<i64>,
pub merge_allowed: Vec<i64>,
pub approval_allowed: Vec<i64>,
}
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<WhitelistSet, GitError> {
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<WhitelistSet, GitError> {
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<Vec<repo_branch_protection_policy::Model>, 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<repo_branch_protection_policy::Model> = 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<String> = 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<WhitelistSet, GitError> {
let ids: Vec<Uuid> = policies.iter().map(|row| row.id).collect();
load_branch_whitelists(db, &ids).await
}