use crate::rpcgit::refs::types::{ListRefsOptions, RefInfo, RefType}; use serde::Serialize; use crate::service::GitService; use crate::share::errors::{GitError, map_baby}; use crate::share::policy::load_branch_policy; use crate::share::validate::validate_qualified_ref; #[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct HeadInfoDto { pub name: String, pub object: String, pub object_type: i8, pub is_detached: bool, } #[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct BranchInfoDto { pub name: String, pub commit_sha: String, pub commit_message: Option, pub is_protected: bool, } #[derive(Debug, Clone, Serialize, utoipa::ToSchema)] pub struct UpdateRefResultDto { pub name: String, pub old_oid: Option, pub new_oid: String, pub protected: bool, } impl GitService { pub async fn refs_list( &self, space_name: String, repo_name: String, ) -> Result, GitError> { let ctx = self.context(space_name, repo_name).await?; self.baby(&ctx) .list_refs(ListRefsOptions::default()) .await .map_err(map_baby) } pub async fn refs_list_filtered( &self, space_name: String, repo_name: String, pattern: String, ) -> Result, GitError> { let ctx = self.context(space_name, repo_name).await?; self.baby(&ctx) .list_refs(ListRefsOptions { patterns: vec![pattern], ..Default::default() }) .await .map_err(map_baby) } pub async fn refs_get( &self, space_name: String, repo_name: String, name: String, ) -> Result { let ctx = self.context(space_name, repo_name).await?; let refs = self .baby(&ctx) .list_refs(ListRefsOptions { patterns: vec![name], ..Default::default() }) .await .map_err(map_baby)?; refs.into_iter().next().ok_or(GitError::NotFound) } pub async fn refs_resolve( &self, space_name: String, repo_name: String, name: String, ) -> Result { let ctx = self.context(space_name, repo_name).await?; self.baby(&ctx) .resolve_revision(&name) .await .map(|oid| oid.to_string()) .map_err(map_baby) } pub async fn refs_head( &self, space_name: String, repo_name: String, ) -> Result { let ctx = self.context(space_name, repo_name).await?; let baby = self.baby(&ctx); let name = baby.get_default_branch().await.map_err(map_baby)?; let object = baby .resolve_revision(&name) .await .map_err(map_baby)? .to_string(); Ok(HeadInfoDto { name, object, object_type: 0, is_detached: false, }) } pub async fn refs_branches( &self, space_name: String, repo_name: String, skip: u64, limit: u64, ) -> Result, GitError> { let ctx = self.context(space_name, repo_name).await?; let baby = self.baby(&ctx); let branches = baby .list_branches(crate::rpcgit::branch::ListBranchesOptions::default()) .await .map_err(map_baby)?; let policy = load_branch_policy(&self.state.database, ctx.model.id).await?; let mut out: Vec = branches .into_iter() .map(|b| BranchInfoDto { name: b.name.clone(), commit_sha: b.sha.to_string(), commit_message: None, is_protected: policy.is_match(&b.name), }) .collect(); out.sort_by(|a, b| a.name.cmp(&b.name)); if let Some(start) = skip.checked_sub(1) { out = out.into_iter().skip(start as usize).collect(); } if limit > 0 { out.truncate(limit as usize); } Ok(out) } pub async fn refs_update( &self, space_name: String, repo_name: String, name: String, new_oid: String, expected_oid: Option, ) -> Result { validate_qualified_ref(&name)?; let ctx = self.context(space_name, repo_name).await?; if self.ref_protected(&ctx, &name).await? { return Err(GitError::Forbidden); } let baby = self.baby(&ctx); let new = crate::service::diffs::parse_oid(&new_oid)?; let old: Option = match expected_oid { Some(hex) => Some(crate::service::diffs::parse_oid(&hex)?), None => None, }; baby.update_ref(&name, new.clone(), old.clone()) .await .map_err(map_baby)?; Ok(UpdateRefResultDto { name, old_oid: old.map(|o| o.to_string()), new_oid: new.to_string(), protected: false, }) } pub async fn refs_delete( &self, space_name: String, repo_name: String, name: String, ) -> Result<(), GitError> { validate_qualified_ref(&name)?; let ctx = self.context(space_name, repo_name).await?; if self.ref_protected(&ctx, &name).await? { return Err(GitError::Forbidden); } self.baby(&ctx).delete_ref(&name).await.map_err(map_baby) } pub async fn refs_is_object_in_ref( &self, space_name: String, repo_name: String, object: String, refname: String, ) -> Result { let ctx = self.context(space_name, repo_name).await?; let oid = crate::service::diffs::parse_oid(&object)?; let refs = self .baby(&ctx) .find_refs_by_oid(oid) .await .map_err(map_baby)?; Ok(refs.into_iter().any(|r| r.name == refname)) } pub async fn refs_classify(&self, name: &str) -> RefType { if name.starts_with("refs/heads/") { RefType::Branch } else if name.starts_with("refs/tags/") { RefType::Tag } else if name.starts_with("refs/remotes/") { RefType::Remote } else { RefType::Other } } async fn ref_protected( &self, ctx: &crate::context::facade::GitContext, name: &str, ) -> Result { if let Some(branch) = name.strip_prefix("refs/heads/") { let policy = load_branch_policy(&self.state.database, ctx.model.id).await?; return Ok(policy.is_match(branch)); } if let Some(tag) = name.strip_prefix("refs/tags/") { let policy = crate::share::policy::load_tag_policy(&self.state.database, ctx.model.id).await?; return Ok(policy.is_match(tag)); } Ok(false) } }