Skip to content
Clone
89109d2
209 lines 6.3 KB Rust
use crate::rpcgit::commit::types::CommitInfo;
use models::pull_request::pull_requests::MergeMethod;
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use uuid::Uuid;
use crate::context::facade::GitContext;
use crate::service::GitService;
use crate::share::errors::{GitError, map_baby};
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergePlanDto {
pub base_sha: String,
pub head_sha: String,
pub merge_base: Option<String>,
pub fast_forwardable: bool,
/// base 上 head 没有的提交数。
pub base_only: i64,
/// head 上 base 没有的提交数。
pub head_only: i64,
pub clean: bool,
pub conflicts: Vec<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergeResultDto {
/// `fast_forward` | `merge_commit` | `squash` | `rebase`
pub method: String,
pub base_ref: String,
pub old_sha: String,
pub new_sha: String,
pub head_sha: String,
/// rebase 时重写出来的提交(按时间正序)。
pub rewritten: Vec<String>,
}
#[derive(Debug, Clone)]
pub struct ExecuteMergeCmd {
pub base_branch: String,
pub head_revision: String,
pub method: MergeMethod,
pub message: Option<String>,
pub caller: i64,
pub allow_ff_only: bool,
pub summary: String,
}
fn hex(oid: &crate::rpcgit::oid::ObjectId) -> String {
oid.to_string()
}
impl GitService {
/// 合并在 KenShell 补齐 MergeService 之前不可用。
pub async fn merge_plan(
&self,
_ctx: &GitContext,
_base: &str,
_head: &str,
) -> Result<MergePlanDto, GitError> {
Err(GitError::Unimplemented(
"merge planning requires KenShell MergeService",
))
}
pub async fn merge_execute(
&self,
_space_name: String,
_repo_name: String,
_cmd: ExecuteMergeCmd,
) -> Result<MergeResultDto, GitError> {
Err(GitError::Unimplemented(
"merge execution requires KenShell MergeService",
))
}
}
#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
pub struct MergeCommitDto {
pub sha: String,
pub subject: String,
pub message: String,
pub author_name: String,
pub author_email: String,
pub author_at: OffsetDateTime,
pub parents: Vec<String>,
}
const MAX_LISTED_COMMITS: u32 = 200;
fn commit_dto(info: CommitInfo) -> MergeCommitDto {
MergeCommitDto {
sha: hex(&info.id),
subject: info.message.lines().next().unwrap_or_default().to_string(),
message: info.message.clone(),
author_name: info.author.name.clone(),
author_email: info.author.email.clone(),
author_at: info.author.time,
parents: info.parents.iter().map(hex).collect(),
}
}
impl GitService {
/// `base..head` 的提交列表(正序,最多 200 条)。
pub async fn merge_commits(
&self,
space_name: String,
repo_name: String,
base: &str,
head: &str,
) -> Result<Vec<MergeCommitDto>, GitError> {
self.commits_between(space_name, repo_name, base, head, MAX_LISTED_COMMITS)
.await
}
pub async fn recent_commits(
&self,
space_name: String,
repo_name: String,
revision: Option<&str>,
limit: u32,
skip: u32,
) -> Result<Vec<MergeCommitDto>, GitError> {
let ctx = self.context(space_name, repo_name).await?;
let reference = match revision.map(str::trim).filter(|value| !value.is_empty()) {
Some(value) => value.to_string(),
None => format!("refs/heads/{}", ctx.model.default_branch),
};
let commits = self
.baby(&ctx)
.list_commits(crate::rpcgit::commit::types::ListCommitsOptions {
revision: reference,
max_count: Some(limit.min(MAX_LISTED_COMMITS).saturating_add(skip)),
skip: Some(skip),
..Default::default()
})
.await
.map_err(map_baby)?;
Ok(commits.into_iter().map(commit_dto).collect())
}
pub async fn commits_between(
&self,
space_name: String,
repo_name: String,
base: &str,
head: &str,
limit: u32,
) -> Result<Vec<MergeCommitDto>, GitError> {
let ctx = self.context(space_name, repo_name).await?;
let baby = self.baby(&ctx);
let base_oid = baby.rev_parse(base).await.map_err(map_baby)?;
let head_oid = baby.rev_parse(head).await.map_err(map_baby)?;
let ids = baby
.commit_ids_between_reverse(
&hex(&base_oid),
&hex(&head_oid),
None,
Some(limit.min(MAX_LISTED_COMMITS)),
)
.await
.map_err(map_baby)?;
let mut out = Vec::with_capacity(ids.len());
for id in ids {
out.push(commit_dto(
baby.get_commit(&hex(&id)).await.map_err(map_baby)?,
));
}
Ok(out)
}
pub async fn tag_target(&self, repo_id: Uuid, tag_name: &str) -> Option<String> {
use models::git::git_tags;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter};
git_tags::Entity::find()
.filter(git_tags::Column::RepoId.eq(repo_id))
.filter(git_tags::Column::Name.eq(tag_name))
.one(self.db_read())
.await
.ok()
.flatten()
.map(|row| row.commit_sha)
}
pub async fn default_branch_base(
&self,
space_name: String,
repo_name: String,
depth: u32,
) -> Result<Option<String>, GitError> {
let ctx = self.context(space_name, repo_name).await?;
let baby = self.baby(&ctx);
let head = baby
.rev_parse(&format!("refs/heads/{}", ctx.model.default_branch))
.await
.map_err(map_baby)?;
let mut cursor = head;
for _ in 0..depth {
let info = match baby.get_commit(&hex(&cursor)).await {
Ok(info) => info,
Err(_) => return Ok(None),
};
match info.parents.first() {
Some(parent) => cursor = parent.clone(),
None => return Ok(None),
}
}
Ok(Some(hex(&cursor)))
}
}