02d8172
use crate::rpcgit::refs::types::{ListRefsOptions, RefInfo};
use serde::Serialize;
use crate::context::facade::GitContext;
use crate::service::GitService;
use crate::share::errors::{GitError, map_baby};
use crate::share::policy::load_branch_policy;
use crate::share::validate;
#[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<String>,
pub is_protected: bool,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct UpdateRefResultDto {
pub name: String,
pub old_oid: Option<String>,
pub new_oid: String,
pub protected: bool,
}
impl GitService {
pub async fn refs_list(&self, ctx: &GitContext) -> Result<Vec<RefInfo>, GitError> {
self.baby(&ctx)
.list_refs(ListRefsOptions::default())
.await
.map_err(map_baby)
}
pub async fn refs_list_filtered(
&self,
ctx: &GitContext,
pattern: String,
) -> Result<Vec<RefInfo>, GitError> {
self.baby(&ctx)
.list_refs(ListRefsOptions {
patterns: vec![pattern],
..Default::default()
})
.await
.map_err(map_baby)
}
pub async fn refs_get(&self, ctx: &GitContext, name: String) -> Result<RefInfo, GitError> {
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, ctx: &GitContext, name: String) -> Result<String, GitError> {
self.baby(&ctx)
.resolve_revision(&name)
.await
.map(|oid| oid.to_string())
.map_err(map_baby)
}
pub async fn refs_head(&self, ctx: &GitContext) -> Result<HeadInfoDto, GitError> {
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,
ctx: &GitContext,
skip: u64,
limit: u64,
) -> Result<Vec<BranchInfoDto>, GitError> {
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.as_ref(), ctx.model.id).await?;
let mut out: Vec<BranchInfoDto> = 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,
ctx: &GitContext,
name: String,
new_oid: String,
expected_oid: Option<String>,
) -> Result<UpdateRefResultDto, GitError> {
validate::validate_qualified_ref(&name)?;
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<crate::rpcgit::oid::ObjectId> = 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, ctx: &GitContext, name: String) -> Result<(), GitError> {
validate::validate_qualified_ref(&name)?;
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,
ctx: &GitContext,
object: String,
refname: String,
) -> Result<bool, GitError> {
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))
}
async fn ref_protected(
&self,
ctx: &crate::context::facade::GitContext,
name: &str,
) -> Result<bool, GitError> {
if let Some(branch) = name.strip_prefix("refs/heads/") {
let policy = load_branch_policy(self.state.database.as_ref(), 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.as_ref(), ctx.model.id)
.await?;
return Ok(policy.is_match(tag));
}
Ok(false)
}
}