gitdataai/libs/service/git/repo.rs
2026-04-15 09:08:09 +08:00

464 lines
15 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use crate::git::MergeOptions;
use models::repos::repo;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct DescriptionQuery {
pub description: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct DescriptionResponse {
pub description: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigGetQuery {
pub key: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigSetRequest {
pub key: String,
pub value: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigDeleteQuery {
pub key: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct ConfigEntriesQuery {
pub prefix: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ConfigEntryResponse {
pub name: String,
pub value: String,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct ConfigSnapshotResponse {
pub entries: Vec<ConfigEntryResponse>,
}
impl From<git::ConfigEntry> for ConfigEntryResponse {
fn from(e: git::ConfigEntry) -> Self {
Self {
name: e.name,
value: e.value,
}
}
}
impl From<git::ConfigSnapshot> for ConfigSnapshotResponse {
fn from(s: git::ConfigSnapshot) -> Self {
Self {
entries: s
.entries
.into_iter()
.map(ConfigEntryResponse::from)
.collect(),
}
}
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct GitUpdateRepoRequest {
pub default_branch: Option<String>,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ConfigBoolResponse {
pub key: String,
pub value: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct MergeAnalysisQuery {
pub their_oid: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct MergeRefAnalysisQuery {
pub ref_name: String,
pub their_oid: String,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct MergeCommitsRequest {
pub local_oid: String,
pub remote_oid: String,
#[serde(default)]
pub find_renames: bool,
#[serde(default)]
pub fail_on_conflict: bool,
#[serde(default)]
pub skip_reuc: bool,
#[serde(default)]
pub no_recursive: bool,
#[serde(default = "default_rename_threshold")]
pub rename_threshold: u32,
#[serde(default)]
pub target_limit: u32,
#[serde(default)]
pub recursion_limit: u32,
}
fn default_rename_threshold() -> u32 {
50
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct MergeTreesRequest {
pub ancestor_oid: String,
pub our_oid: String,
pub their_oid: String,
#[serde(default)]
pub find_renames: bool,
#[serde(default)]
pub fail_on_conflict: bool,
#[serde(default)]
pub skip_reuc: bool,
#[serde(default)]
pub no_recursive: bool,
#[serde(default = "default_rename_threshold")]
pub rename_threshold: u32,
#[serde(default)]
pub target_limit: u32,
#[serde(default)]
pub recursion_limit: u32,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergeAnalysisResponse {
pub analysis: MergeAnalysisResultInner,
pub preference: MergePreferenceResultInner,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergeAnalysisResultInner {
pub is_none: bool,
pub is_normal: bool,
pub is_up_to_date: bool,
pub is_fast_forward: bool,
pub is_unborn: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergePreferenceResultInner {
pub is_none: bool,
pub is_no_fast_forward: bool,
pub is_fastforward_only: bool,
}
impl From<git::MergeAnalysisResult> for MergeAnalysisResultInner {
fn from(r: git::MergeAnalysisResult) -> Self {
Self {
is_none: r.is_none,
is_normal: r.is_normal,
is_up_to_date: r.is_up_to_date,
is_fast_forward: r.is_fast_forward,
is_unborn: r.is_unborn,
}
}
}
impl From<git::MergePreferenceResult> for MergePreferenceResultInner {
fn from(r: git::MergePreferenceResult) -> Self {
Self {
is_none: r.is_none,
is_no_fast_forward: r.is_no_fast_forward,
is_fastforward_only: r.is_fastforward_only,
}
}
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct MergeheadInfoResponse {
pub oid: String,
}
impl From<git::MergeheadInfo> for MergeheadInfoResponse {
fn from(h: git::MergeheadInfo) -> Self {
Self {
oid: h.oid.to_string(),
}
}
}
impl AppService {
pub async fn git_description_get(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<DescriptionResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
let description = domain.description_get()?;
Ok(DescriptionResponse { description })
}
pub async fn git_description_set(
&self,
namespace: String,
repo_name: String,
query: DescriptionQuery,
ctx: &Session,
) -> Result<DescriptionResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
domain.description_set(&query.description)?;
Ok(DescriptionResponse {
description: query.description,
})
}
pub async fn git_description_reset(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<DescriptionResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
domain.description_reset()?;
Ok(DescriptionResponse {
description: "Unnamed repository".to_string(),
})
}
pub async fn git_description_exists(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<bool, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
Ok(domain.description_exists())
}
pub async fn git_config_entries(
&self,
namespace: String,
repo_name: String,
query: ConfigEntriesQuery,
ctx: &Session,
) -> Result<ConfigSnapshotResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
let snapshot = domain
.config_entries(query.prefix.as_deref())
.map_err(AppError::from)?;
Ok(ConfigSnapshotResponse::from(snapshot))
}
pub async fn git_config_get(
&self,
namespace: String,
repo_name: String,
query: ConfigGetQuery,
ctx: &Session,
) -> Result<Option<String>, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
domain.config_get(&query.key).map_err(AppError::from)
}
pub async fn git_config_set(
&self,
namespace: String,
repo_name: String,
request: ConfigSetRequest,
ctx: &Session,
) -> Result<(), AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
domain
.config_set(&request.key, &request.value)
.map_err(AppError::from)
}
pub async fn git_config_delete(
&self,
namespace: String,
repo_name: String,
query: ConfigDeleteQuery,
ctx: &Session,
) -> Result<(), AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
domain.config_delete(&query.key).map_err(AppError::from)
}
pub async fn git_config_has(
&self,
namespace: String,
repo_name: String,
query: ConfigGetQuery,
ctx: &Session,
) -> Result<ConfigBoolResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
let exists = domain.config_has(&query.key).map_err(AppError::from)?;
Ok(ConfigBoolResponse {
key: query.key,
value: exists,
})
}
pub async fn git_merge_analysis(
&self,
namespace: String,
repo_name: String,
query: MergeAnalysisQuery,
ctx: &Session,
) -> Result<MergeAnalysisResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
let their_oid = git::CommitOid::new(&query.their_oid);
let (analysis, preference) = domain.merge_analysis(&their_oid).map_err(AppError::from)?;
Ok(MergeAnalysisResponse {
analysis: MergeAnalysisResultInner::from(analysis),
preference: MergePreferenceResultInner::from(preference),
})
}
pub async fn git_merge_analysis_for_ref(
&self,
namespace: String,
repo_name: String,
query: MergeRefAnalysisQuery,
ctx: &Session,
) -> Result<MergeAnalysisResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
let their_oid = git::CommitOid::new(&query.their_oid);
let (analysis, preference) = domain
.merge_analysis_for_ref(&query.ref_name, &their_oid)
.map_err(AppError::from)?;
Ok(MergeAnalysisResponse {
analysis: MergeAnalysisResultInner::from(analysis),
preference: MergePreferenceResultInner::from(preference),
})
}
pub async fn git_merge_base(
&self,
namespace: String,
repo_name: String,
oid1: String,
oid2: String,
ctx: &Session,
) -> Result<String, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
let base = domain
.merge_base(&git::CommitOid::new(&oid1), &git::CommitOid::new(&oid2))
.map_err(AppError::from)?;
Ok(base.to_string())
}
pub async fn git_merge_commits(
&self,
namespace: String,
repo_name: String,
request: MergeCommitsRequest,
ctx: &Session,
) -> Result<(), AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let mut opts = MergeOptions::new();
opts = opts.find_renames(request.find_renames);
opts = opts.fail_on_conflict(request.fail_on_conflict);
opts = opts.skip_reuc(request.skip_reuc);
opts = opts.no_recursive(request.no_recursive);
opts = opts.rename_threshold(request.rename_threshold);
opts = opts.target_limit(request.target_limit);
opts = opts.recursion_limit(request.recursion_limit);
let domain = git::GitDomain::from_model(repo)?;
domain
.merge_commits(
&git::CommitOid::new(&request.local_oid),
&git::CommitOid::new(&request.remote_oid),
Some(opts),
)
.map_err(AppError::from)
}
pub async fn git_merge_trees(
&self,
namespace: String,
repo_name: String,
request: MergeTreesRequest,
ctx: &Session,
) -> Result<(), AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let mut opts = MergeOptions::new();
opts = opts.find_renames(request.find_renames);
opts = opts.fail_on_conflict(request.fail_on_conflict);
opts = opts.skip_reuc(request.skip_reuc);
opts = opts.no_recursive(request.no_recursive);
opts = opts.rename_threshold(request.rename_threshold);
opts = opts.target_limit(request.target_limit);
opts = opts.recursion_limit(request.recursion_limit);
let domain = git::GitDomain::from_model(repo)?;
domain
.merge_trees(
&git::CommitOid::new(&request.ancestor_oid),
&git::CommitOid::new(&request.our_oid),
&git::CommitOid::new(&request.their_oid),
Some(opts),
)
.map_err(AppError::from)
}
pub async fn git_merge_abort(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<(), AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
domain.merge_abort().map_err(AppError::from)
}
pub async fn git_merge_is_in_progress(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<bool, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
Ok(domain.merge_is_in_progress())
}
pub async fn git_mergehead_list(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<Vec<MergeheadInfoResponse>, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let mut domain = git::GitDomain::from_model(repo)?;
let heads = domain.mergehead_list().map_err(AppError::from)?;
Ok(heads.into_iter().map(MergeheadInfoResponse::from).collect())
}
pub async fn git_merge_is_conflicted(
&self,
namespace: String,
repo_name: String,
ctx: &Session,
) -> Result<bool, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let domain = git::GitDomain::from_model(repo)?;
Ok(domain.merge_is_conflicted())
}
pub async fn git_update_repo(
&self,
namespace: String,
repo_name: String,
params: GitUpdateRepoRequest,
ctx: &Session,
) -> Result<(), AppError> {
let repo = self
.utils_find_repo(namespace.clone(), repo_name.clone(), ctx)
.await?;
let txn = self.db.begin().await?;
let mut active: repo::ActiveModel = repo.clone().into_active_model();
if let Some(default_branch) = params.default_branch {
active.default_branch = Set(default_branch);
}
active.update(&txn).await?;
txn.commit().await?;
Ok(())
}
}