pub use crate::context::facade::GitContext; pub use crate::handler::RepoPath; pub use crate::service::GitService; pub use crate::service::commits::{CommitStatusDto, CommitStatusDtoState}; pub use crate::service::diffs::{ChangedPathDiffDto, DiffOptionsDto}; pub use crate::service::merge::{ExecuteMergeCmd, MergeCommitDto, MergePlanDto, MergeResultDto}; pub use crate::share::access::authorize_repo_access; pub use crate::share::errors::GitError; use models::repo::repos; use sea_orm::{ActiveModelTrait, ConnectionTrait, EntityTrait, QuerySelect, Set}; use uuid::Uuid; pub struct RepositoryInfo { pub lower_name: String, pub is_private: bool, } impl GitService { pub async fn repository(&self, id: Uuid) -> Result, GitError> { let Some(repo) = repos::Entity::find_by_id(id) .one(self.database.read()) .await? else { return Ok(None); }; Ok(Some(RepositoryInfo { lower_name: repo.lower_name, is_private: repo.is_private, })) } pub async fn repository_visible_from_space( &self, id: Uuid, space_id: Uuid, ) -> Result { let Some(repo) = repos::Entity::find_by_id(id) .one(self.database.read()) .await? else { return Ok(false); }; if !repo.is_private { return Ok(true); } Ok(self .access .for_dir(repo.dir_id) .await? .is_some_and(|space| space.id() == space_id)) } /// 合并要求:规则全部来自 KenShell 的保护视图。 pub async fn merge_requirements( &self, ctx: &GitContext, branch: &str, caller: i64, ) -> Result, GitError> { let rows = self .baby(ctx) .list_ref_protections("branch") .await .map_err(crate::share::errors::map_baby)?; let matched: Vec<_> = rows .into_iter() .filter(|view| pattern_matches(&view.pattern, branch)) .collect(); if matched.is_empty() { return Ok(None); } let rules: Vec = matched .iter() .map(|view| view.rules.clone().unwrap_or_default()) .collect(); let merge_allowed: Vec = matched .iter() .flat_map(|view| view.whitelists.iter()) .filter(|entry| entry.kind == proto::kenshell::v1::WhitelistKind::User as i32) .map(|entry| entry.user_id) .collect(); Ok(Some(MergeRequirements { required_approvals: rules .iter() .map(|rule| rule.required_approvals) .max() .unwrap_or(0), dismiss_stale_approvals: rules.iter().any(|rule| rule.dismiss_stale_approvals), whitelist_bypass: rules.iter().any(|rule| rule.enable_merge_whitelist) && merge_allowed.contains(&caller), block_on_rejected_reviews: rules.iter().any(|rule| rule.block_on_rejected_reviews), block_on_change_requested_reviews: rules .iter() .any(|rule| rule.block_on_change_requested_reviews), block_on_outdated_branch: rules.iter().any(|rule| rule.block_on_outdated_branch), require_signed_commits: rules.iter().any(|rule| rule.require_signed_commits), required_status_contexts: rules .iter() .flat_map(|rule| { rule.required_status_contexts .split(['\n', ',']) .map(str::trim) .filter(|value| !value.is_empty()) .map(str::to_owned) }) .collect(), protected_file_patterns: rules .iter() .map(|rule| rule.protected_file_patterns.as_str()) .collect::>() .join(","), unprotected_file_patterns: rules .iter() .map(|rule| rule.unprotected_file_patterns.as_str()) .collect::>() .join(","), })) } pub async fn lock_repository( &self, tx: &impl ConnectionTrait, id: Uuid, ) -> Result<(), GitError> { repos::Entity::find_by_id(id) .lock_exclusive() .one(tx) .await? .ok_or(GitError::NotFound)?; Ok(()) } pub async fn set_pull_count(&self, id: Uuid, count: i32) -> Result<(), GitError> { repos::ActiveModel { id: Set(id), nums_pr: Set(count), ..Default::default() } .update(self.database.write()) .await?; Ok(()) } } /// 用 glob 判断保护模式是否命中分支名。 fn pattern_matches(pattern: &str, branch: &str) -> bool { globset::Glob::new(pattern) .map(|glob| glob.compile_matcher().is_match(branch)) .unwrap_or(false) } pub struct MergeRequirements { pub required_approvals: i32, pub dismiss_stale_approvals: bool, pub whitelist_bypass: bool, pub block_on_rejected_reviews: bool, pub block_on_change_requested_reviews: bool, pub block_on_outdated_branch: bool, pub require_signed_commits: bool, pub required_status_contexts: Vec, pub protected_file_patterns: String, pub unprotected_file_patterns: String, }