327df0c
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<Option<RepositoryInfo>, 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<bool, GitError> {
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<Option<MergeRequirements>, 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<proto::kenshell::v1::RefProtectionRules> = matched
.iter()
.map(|view| view.rules.clone().unwrap_or_default())
.collect();
let merge_allowed: Vec<i64> = 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::<Vec<_>>()
.join(","),
unprotected_file_patterns: rules
.iter()
.map(|rule| rule.unprotected_file_patterns.as_str())
.collect::<Vec<_>>()
.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<String>,
pub protected_file_patterns: String,
pub unprotected_file_patterns: String,
}