gitdataai/libs/service/git/commit.rs
2026-04-14 19:02:01 +08:00

1362 lines
39 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use crate::git::{
CommitDiffFile, CommitDiffHunk, CommitDiffStats, CommitGraph, CommitMeta, CommitRefInfo,
CommitReflogEntry, CommitSignature, CommitSort, CommitWalkOptions,
};
use models::repos::repo;
use redis::AsyncCommands;
use serde::{Deserialize, Serialize};
use session::Session;
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct CommitGetQuery {
pub oid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitMetaResponse {
pub oid: String,
pub message: String,
pub summary: String,
pub author: CommitSignatureResponse,
pub committer: CommitSignatureResponse,
pub tree_id: String,
pub parent_ids: Vec<String>,
pub encoding: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitSignatureResponse {
pub name: String,
pub email: String,
pub time_secs: i64,
pub offset_minutes: i32,
}
impl From<CommitSignature> for CommitSignatureResponse {
fn from(s: CommitSignature) -> Self {
Self {
name: s.name,
email: s.email,
time_secs: s.time_secs,
offset_minutes: s.offset_minutes,
}
}
}
impl From<CommitMeta> for CommitMetaResponse {
fn from(c: CommitMeta) -> Self {
Self {
oid: c.oid.to_string(),
message: c.message,
summary: c.summary,
author: CommitSignatureResponse::from(c.author),
committer: CommitSignatureResponse::from(c.committer),
tree_id: c.tree_id.to_string(),
parent_ids: c.parent_ids.into_iter().map(|p| p.to_string()).collect(),
encoding: c.encoding,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitExistsResponse {
pub oid: String,
pub exists: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitIsCommitResponse {
pub oid: String,
pub is_commit: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitMessageResponse {
pub oid: String,
pub message: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitSummaryResponse {
pub oid: String,
pub summary: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitShortIdResponse {
pub oid: String,
pub short_id: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitAuthorResponse {
pub oid: String,
pub author: CommitSignatureResponse,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitTreeIdResponse {
pub oid: String,
pub tree_id: String,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitParentCountResponse {
pub oid: String,
pub parent_count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitParentIdsResponse {
pub oid: String,
pub parent_ids: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitIsMergeResponse {
pub oid: String,
pub is_merge: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitIsTipResponse {
pub oid: String,
pub is_tip: bool,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitRefCountResponse {
pub oid: String,
pub ref_count: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitCountResponse {
pub count: usize,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitRefInfoResponse {
pub name: String,
pub target: String,
pub is_remote: bool,
pub is_tag: bool,
}
impl From<CommitRefInfo> for CommitRefInfoResponse {
fn from(r: CommitRefInfo) -> Self {
Self {
name: r.name,
target: r.target.to_string(),
is_remote: r.is_remote,
is_tag: r.is_tag,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitBranchesResponse {
pub oid: String,
pub branches: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitTagsResponse {
pub oid: String,
pub tags: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitReflogEntryResponse {
pub oid_new: String,
pub oid_old: String,
pub committer_name: String,
pub committer_email: String,
pub time_secs: i64,
pub message: Option<String>,
}
impl From<CommitReflogEntry> for CommitReflogEntryResponse {
fn from(e: CommitReflogEntry) -> Self {
Self {
oid_new: e.oid_new.to_string(),
oid_old: e.oid_old.to_string(),
committer_name: e.committer_name,
committer_email: e.committer_email,
time_secs: e.time_secs,
message: e.message,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitGraphResponse {
pub lines: Vec<CommitGraphLineResponse>,
pub max_parents: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CommitGraphLineResponse {
pub oid: String,
pub graph_chars: String,
pub refs: String,
pub short_message: String,
}
impl From<git::CommitGraphLine> for CommitGraphLineResponse {
fn from(l: git::CommitGraphLine) -> Self {
Self {
oid: l.oid.to_string(),
graph_chars: l.graph_chars,
refs: l.refs,
short_message: l.short_message,
}
}
}
impl From<CommitGraph> for CommitGraphResponse {
fn from(g: CommitGraph) -> Self {
Self {
lines: g
.lines
.into_iter()
.map(CommitGraphLineResponse::from)
.collect(),
max_parents: g.max_parents,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitDiffStatsResponse {
pub oid: String,
pub files_changed: usize,
pub insertions: usize,
pub deletions: usize,
}
impl From<CommitDiffStats> for CommitDiffStatsResponse {
fn from(s: CommitDiffStats) -> Self {
Self {
oid: String::new(),
files_changed: s.files_changed,
insertions: s.insertions,
deletions: s.deletions,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitDiffFileResponse {
pub path: Option<String>,
pub status: String,
pub is_binary: bool,
pub size: u64,
}
impl From<CommitDiffFile> for CommitDiffFileResponse {
fn from(f: CommitDiffFile) -> Self {
Self {
path: f.path,
status: f.status,
is_binary: f.is_binary,
size: f.size,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitDiffHunkResponse {
pub old_start: u32,
pub old_lines: u32,
pub new_start: u32,
pub new_lines: u32,
pub header: String,
}
impl From<CommitDiffHunk> for CommitDiffHunkResponse {
fn from(h: CommitDiffHunk) -> Self {
Self {
old_start: h.old_start,
old_lines: h.old_lines,
new_start: h.new_start,
new_lines: h.new_lines,
header: h.header,
}
}
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitLogQuery {
pub rev: Option<String>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitWalkQuery {
pub rev: Option<String>,
pub limit: Option<usize>,
#[serde(default)]
pub first_parent_only: bool,
#[serde(default)]
pub topological: bool,
#[serde(default)]
pub reverse: bool,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitAncestorsQuery {
pub oid: String,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitDescendantsQuery {
pub oid: String,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitResolveQuery {
pub rev: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitCherryPickRequest {
pub cherrypick_oid: String,
pub author_name: String,
pub author_email: String,
pub committer_name: String,
pub committer_email: String,
pub message: Option<String>,
pub mainline: Option<u32>,
pub update_ref: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitCherryPickAbortRequest {
pub reset_type: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitRevertRequest {
pub revert_oid: String,
pub author_name: String,
pub author_email: String,
pub committer_name: String,
pub committer_email: String,
pub message: Option<String>,
pub mainline: Option<u32>,
pub update_ref: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitRevertAbortRequest {
pub reset_type: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct CommitCreateResponse {
pub oid: String,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitCreateRequest {
pub author_name: String,
pub author_email: String,
pub committer_name: String,
pub committer_email: String,
pub message: String,
pub tree_id: String,
pub parent_ids: Vec<String>,
pub update_ref: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitAmendRequest {
pub oid: String,
pub author_name: Option<String>,
pub author_email: Option<String>,
pub committer_name: Option<String>,
pub committer_email: Option<String>,
pub message: Option<String>,
pub message_encoding: Option<String>,
pub tree_id: Option<String>,
pub update_ref: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitDiffQuery {
pub oid: String,
}
macro_rules! git_spawn {
($repo:expr, $domain:ident -> $body:expr) => {{
let repo_clone = $repo.clone();
tokio::task::spawn_blocking(move || {
let $domain = git::GitDomain::from_model(repo_clone)?;
$body
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)
}};
}
impl AppService {
pub async fn git_commit_get(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitMetaResponse, AppError> {
let cache_key = format!(
"git:commit:get:{}:{}:{}",
namespace, repo_name, query.oid
);
if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await {
if let Ok(cached) = serde_json::from_str(&cached) {
return Ok(cached);
}
}
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let meta = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_get(&oid)
})?;
let response = CommitMetaResponse::from(meta);
if let Ok(mut conn) = self.cache.conn().await {
let _: Option<()> = conn
.set_ex::<String, String, ()>(
cache_key,
serde_json::to_string(&response).unwrap_or_default(),
3600,
)
.await
.ok();
}
Ok(response)
}
pub async fn git_commit_exists(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitExistsResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let exists = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
Ok::<_, git::GitError>(domain.commit_exists(&oid))
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(CommitExistsResponse {
oid: query.oid,
exists,
})
}
pub async fn git_commit_is_commit(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitIsCommitResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let is_commit = tokio::task::spawn_blocking(move || {
let domain = git::GitDomain::from_model(repo)?;
let oid = git::CommitOid::new(&oid_str);
Ok::<_, git::GitError>(domain.commit_is_commit(&oid))
})
.await
.map_err(|e| AppError::InternalServerError(format!("Task join error: {}", e)))?
.map_err(AppError::from)?;
Ok(CommitIsCommitResponse {
oid: query.oid,
is_commit,
})
}
pub async fn git_commit_message(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitMessageResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let message = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_message(&oid)
})?;
Ok(CommitMessageResponse {
oid: query.oid,
message,
})
}
pub async fn git_commit_summary(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitSummaryResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let summary = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_summary(&oid)
})?;
Ok(CommitSummaryResponse {
oid: query.oid,
summary,
})
}
pub async fn git_commit_short_id(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitShortIdResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let short_id = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_short_id(&oid)
})?;
Ok(CommitShortIdResponse {
oid: query.oid,
short_id,
})
}
pub async fn git_commit_author(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitAuthorResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let author = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_author(&oid)
})?;
Ok(CommitAuthorResponse {
oid: query.oid,
author: CommitSignatureResponse::from(author),
})
}
pub async fn git_commit_tree_id(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitTreeIdResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let tree_id = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_tree_id(&oid)
})?;
Ok(CommitTreeIdResponse {
oid: query.oid,
tree_id: tree_id.to_string(),
})
}
pub async fn git_commit_parent_count(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitParentCountResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let parent_count = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_parent_count(&oid)
})?;
Ok(CommitParentCountResponse {
oid: query.oid,
parent_count,
})
}
pub async fn git_commit_parent_ids(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitParentIdsResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let parent_ids = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_parent_ids(&oid)
})?;
Ok(CommitParentIdsResponse {
oid: query.oid,
parent_ids: parent_ids.into_iter().map(|p| p.to_string()).collect(),
})
}
pub async fn git_commit_parent(
&self,
namespace: String,
repo_name: String,
oid: String,
index: usize,
ctx: &Session,
) -> Result<CommitMetaResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = oid.clone();
let parent = git_spawn!(repo, domain -> {
let commit_oid = git::CommitOid::new(&oid_str);
domain.commit_parent(&commit_oid, index)
})?;
Ok(CommitMetaResponse::from(parent))
}
pub async fn git_commit_first_parent(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<Option<CommitMetaResponse>, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let parent = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_first_parent(&oid)
})?;
Ok(parent.map(CommitMetaResponse::from))
}
pub async fn git_commit_is_merge(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitIsMergeResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let is_merge = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_is_merge(&oid)
})?;
Ok(CommitIsMergeResponse {
oid: query.oid,
is_merge,
})
}
pub async fn git_commit_log(
&self,
namespace: String,
repo_name: String,
query: CommitLogQuery,
ctx: &Session,
) -> Result<Vec<CommitMetaResponse>, AppError> {
let cache_key = format!(
"git:commit:log:{}:{}:{:?}:{}",
namespace,
repo_name,
query.rev,
query.limit.unwrap_or(0),
);
if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await {
if let Ok(cached) = serde_json::from_str(&cached) {
return Ok(cached);
}
}
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let rev_clone = query.rev.clone();
let limit = query.limit.unwrap_or(0);
let commits = git_spawn!(repo, domain -> {
domain.commit_log(rev_clone.as_deref(), limit)
})?;
let response: Vec<CommitMetaResponse> =
commits.into_iter().map(CommitMetaResponse::from).collect();
if let Ok(mut conn) = self.cache.conn().await {
let _: Option<()> = conn
.set_ex::<String, String, ()>(
cache_key,
serde_json::to_string(&response).unwrap_or_default(),
300,
)
.await
.ok();
}
Ok(response)
}
pub async fn git_commit_count(
&self,
namespace: String,
repo_name: String,
from: Option<String>,
to: Option<String>,
ctx: &Session,
) -> Result<CommitCountResponse, AppError> {
let cache_key = format!(
"git:commit:count:{}:{}:{:?}:{:?}",
namespace, repo_name, from, to,
);
if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await {
if let Ok(cached) = serde_json::from_str(&cached) {
return Ok(cached);
}
}
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let from_clone = from.clone();
let to_clone = to.clone();
let count = git_spawn!(repo, domain -> {
domain.commit_count(from_clone.as_deref(), to_clone.as_deref())
})?;
let response = CommitCountResponse { count };
if let Ok(mut conn) = self.cache.conn().await {
let _: Option<()> = conn
.set_ex::<String, String, ()>(
cache_key,
serde_json::to_string(&response).unwrap_or_default(),
300,
)
.await
.ok();
}
Ok(response)
}
pub async fn git_commit_refs(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<Vec<CommitRefInfoResponse>, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let refs = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_refs(&oid)
})?;
Ok(refs.into_iter().map(CommitRefInfoResponse::from).collect())
}
pub async fn git_commit_branches(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitBranchesResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let branches = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_branches(&oid)
})?;
Ok(CommitBranchesResponse {
oid: query.oid,
branches,
})
}
pub async fn git_commit_tags(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitTagsResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let tags = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_tags(&oid)
})?;
Ok(CommitTagsResponse {
oid: query.oid,
tags,
})
}
pub async fn git_commit_is_tip(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitIsTipResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let is_tip = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_is_tip(&oid)
})?;
Ok(CommitIsTipResponse {
oid: query.oid,
is_tip,
})
}
pub async fn git_commit_ref_count(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
ctx: &Session,
) -> Result<CommitRefCountResponse, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let ref_count = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_ref_count(&oid)
})?;
Ok(CommitRefCountResponse {
oid: query.oid,
ref_count,
})
}
pub async fn git_commit_reflog(
&self,
namespace: String,
repo_name: String,
query: CommitGetQuery,
refname: Option<String>,
ctx: &Session,
) -> Result<Vec<CommitReflogEntryResponse>, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let refname_clone = refname.clone();
let entries = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_reflog(&oid, refname_clone.as_deref())
})?;
Ok(entries
.into_iter()
.map(CommitReflogEntryResponse::from)
.collect())
}
pub async fn git_commit_graph(
&self,
namespace: String,
repo_name: String,
query: CommitWalkQuery,
ctx: &Session,
) -> Result<CommitGraphResponse, AppError> {
let cache_key = format!(
"git:commit:graph:{}:{}:{:?}:{}",
namespace,
repo_name,
query.rev,
query.limit.unwrap_or(0),
);
if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await {
if let Ok(cached) = serde_json::from_str(&cached) {
return Ok(cached);
}
}
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let rev_clone = query.rev.clone();
let limit = query.limit.unwrap_or(0);
let graph = git_spawn!(repo, domain -> {
domain.commit_graph_simple(rev_clone.as_deref(), limit)
})?;
let response = CommitGraphResponse::from(graph);
if let Ok(mut conn) = self.cache.conn().await {
let _: Option<()> = conn
.set_ex::<String, String, ()>(
cache_key,
serde_json::to_string(&response).unwrap_or_default(),
300,
)
.await
.ok();
}
Ok(response)
}
pub async fn git_commit_walk(
&self,
namespace: String,
repo_name: String,
query: CommitWalkQuery,
ctx: &Session,
) -> Result<Vec<CommitMetaResponse>, AppError> {
let cache_key = format!(
"git:commit:walk:{}:{}:{:?}:{}:{}:{}:{}",
namespace,
repo_name,
query.rev,
query.limit.unwrap_or(0),
query.first_parent_only,
query.topological,
query.reverse,
);
if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await {
if let Ok(cached) = serde_json::from_str(&cached) {
return Ok(cached);
}
}
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let rev_clone = query.rev.clone();
let limit = query.limit.unwrap_or(0);
let first_parent_only = query.first_parent_only;
let topological = query.topological;
let reverse = query.reverse;
let sort = if topological && reverse {
CommitSort(CommitSort::TOPOLOGICAL.0 | CommitSort::TIME.0 | CommitSort::REVERSE.0)
} else if topological {
CommitSort(CommitSort::TOPOLOGICAL.0 | CommitSort::TIME.0)
} else if reverse {
CommitSort(CommitSort::TIME.0 | CommitSort::REVERSE.0)
} else {
CommitSort(CommitSort::TOPOLOGICAL.0 | CommitSort::TIME.0)
};
let commits = git_spawn!(repo, domain -> {
domain.commit_walk(CommitWalkOptions {
rev: rev_clone,
sort,
limit,
first_parent_only,
})
})?;
let response: Vec<CommitMetaResponse> =
commits.into_iter().map(CommitMetaResponse::from).collect();
if let Ok(mut conn) = self.cache.conn().await {
let _: Option<()> = conn
.set_ex::<String, String, ()>(
cache_key,
serde_json::to_string(&response).unwrap_or_default(),
300,
)
.await
.ok();
}
Ok(response)
}
pub async fn git_commit_ancestors(
&self,
namespace: String,
repo_name: String,
query: CommitAncestorsQuery,
ctx: &Session,
) -> Result<Vec<CommitMetaResponse>, AppError> {
let cache_key = format!(
"git:commit:ancestors:{}:{}:{}:{}",
namespace,
repo_name,
query.oid,
query.limit.unwrap_or(0),
);
if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await {
if let Ok(cached) = serde_json::from_str(&cached) {
return Ok(cached);
}
}
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let limit = query.limit.unwrap_or(0);
let commits = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_ancestors(&oid, limit)
})?;
let response: Vec<CommitMetaResponse> =
commits.into_iter().map(CommitMetaResponse::from).collect();
if let Ok(mut conn) = self.cache.conn().await {
let _: Option<()> = conn
.set_ex::<String, String, ()>(
cache_key,
serde_json::to_string(&response).unwrap_or_default(),
300,
)
.await
.ok();
}
Ok(response)
}
pub async fn git_commit_descendants(
&self,
namespace: String,
repo_name: String,
query: CommitDescendantsQuery,
ctx: &Session,
) -> Result<Vec<CommitMetaResponse>, AppError> {
let cache_key = format!(
"git:commit:descendants:{}:{}:{}:{}",
namespace,
repo_name,
query.oid,
query.limit.unwrap_or(0),
);
if let Ok(mut conn) = self.cache.conn().await {
if let Ok(cached) = conn.get::<_, String>(cache_key.clone()).await {
if let Ok(cached) = serde_json::from_str(&cached) {
return Ok(cached);
}
}
}
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let oid_str = query.oid.clone();
let limit = query.limit.unwrap_or(0);
let commits = git_spawn!(repo, domain -> {
let oid = git::CommitOid::new(&oid_str);
domain.commit_descendants(&oid, limit)
})?;
let response: Vec<CommitMetaResponse> =
commits.into_iter().map(CommitMetaResponse::from).collect();
if let Ok(mut conn) = self.cache.conn().await {
let _: Option<()> = conn
.set_ex::<String, String, ()>(
cache_key,
serde_json::to_string(&response).unwrap_or_default(),
300,
)
.await
.ok();
}
Ok(response)
}
pub async fn git_commit_resolve_rev(
&self,
namespace: String,
repo_name: String,
query: CommitResolveQuery,
ctx: &Session,
) -> Result<String, AppError> {
let repo = self.utils_find_repo(namespace, repo_name, ctx).await?;
let rev_str = query.rev.clone();
let oid = git_spawn!(repo, domain -> {
domain.resolve_rev(&rev_str)
})?;
Ok(oid.to_string())
}
pub async fn git_commit_create(
&self,
namespace: String,
repo_name: String,
request: CommitCreateRequest,
ctx: &Session,
) -> Result<CommitCreateResponse, AppError> {
let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?;
let parent_ids: Vec<_> = request
.parent_ids
.iter()
.map(|p| git::CommitOid::new(p))
.collect();
let author = git::CommitSignature {
name: request.author_name,
email: request.author_email,
time_secs: chrono::Utc::now().timestamp(),
offset_minutes: 0,
};
let committer = git::CommitSignature {
name: request.committer_name,
email: request.committer_email,
time_secs: chrono::Utc::now().timestamp(),
offset_minutes: 0,
};
let tree_id = git::CommitOid::new(&request.tree_id);
let update_ref = request.update_ref.clone();
let oid = git_spawn!(repo, domain -> {
domain.commit_create(
update_ref.as_deref(),
&author,
&committer,
&request.message,
&tree_id,
&parent_ids,
)
})?;
Ok(CommitCreateResponse {
oid: oid.to_string(),
})
}
pub async fn git_commit_amend(
&self,
namespace: String,
repo_name: String,
request: CommitAmendRequest,
ctx: &Session,
) -> Result<CommitCreateResponse, AppError> {
let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?;
let oid = git::CommitOid::new(&request.oid);
let author = if request.author_name.is_some() && request.author_email.is_some() {
Some(git::CommitSignature {
name: request.author_name.unwrap(),
email: request.author_email.unwrap(),
time_secs: 0,
offset_minutes: 0,
})
} else {
None
};
let committer =
if request.committer_name.is_some() && request.committer_email.is_some() {
Some(git::CommitSignature {
name: request.committer_name.unwrap(),
email: request.committer_email.unwrap(),
time_secs: 0,
offset_minutes: 0,
})
} else {
None
};
let tree_id = request.tree_id.as_ref().map(|t| git::CommitOid::new(t));
let update_ref = request.update_ref.clone();
let message_encoding = request.message_encoding.clone();
let message = request.message.clone();
let new_oid = git_spawn!(repo, domain -> {
domain.commit_amend(
&oid,
update_ref.as_deref(),
author.as_ref(),
committer.as_ref(),
message_encoding.as_deref(),
message.as_deref(),
tree_id.as_ref(),
)
})?;
Ok(CommitCreateResponse {
oid: new_oid.to_string(),
})
}
pub async fn git_commit_cherry_pick(
&self,
namespace: String,
repo_name: String,
request: CommitCherryPickRequest,
ctx: &Session,
) -> Result<CommitCreateResponse, AppError> {
let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?;
let cherrypick_oid = git::CommitOid::new(&request.cherrypick_oid);
let author = git::CommitSignature {
name: request.author_name,
email: request.author_email,
time_secs: chrono::Utc::now().timestamp(),
offset_minutes: 0,
};
let committer = git::CommitSignature {
name: request.committer_name,
email: request.committer_email,
time_secs: chrono::Utc::now().timestamp(),
offset_minutes: 0,
};
let message = request.message.clone();
let mainline = request.mainline.unwrap_or(0);
let update_ref = request.update_ref.clone();
let oid = git_spawn!(repo, domain -> {
domain.commit_cherry_pick(
&cherrypick_oid,
&author,
&committer,
message.as_deref(),
mainline,
update_ref.as_deref(),
)
})?;
Ok(CommitCreateResponse {
oid: oid.to_string(),
})
}
pub async fn git_commit_cherry_pick_abort(
&self,
namespace: String,
repo_name: String,
request: CommitCherryPickAbortRequest,
ctx: &Session,
) -> Result<(), AppError> {
let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?;
let reset_type = request.reset_type.clone();
git_spawn!(repo, domain -> {
domain.commit_cherry_pick_abort(reset_type.as_deref().unwrap_or("hard"))
})?;
Ok(())
}
pub async fn git_commit_revert(
&self,
namespace: String,
repo_name: String,
request: CommitRevertRequest,
ctx: &Session,
) -> Result<CommitCreateResponse, AppError> {
let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?;
let revert_oid = git::CommitOid::new(&request.revert_oid);
let author = git::CommitSignature {
name: request.author_name,
email: request.author_email,
time_secs: chrono::Utc::now().timestamp(),
offset_minutes: 0,
};
let committer = git::CommitSignature {
name: request.committer_name,
email: request.committer_email,
time_secs: chrono::Utc::now().timestamp(),
offset_minutes: 0,
};
let message = request.message.clone();
let mainline = request.mainline.unwrap_or(0);
let update_ref = request.update_ref.clone();
let oid = git_spawn!(repo, domain -> {
domain.commit_revert(
&revert_oid,
&author,
&committer,
message.as_deref(),
mainline,
update_ref.as_deref(),
)
})?;
Ok(CommitCreateResponse {
oid: oid.to_string(),
})
}
pub async fn git_commit_revert_abort(
&self,
namespace: String,
repo_name: String,
request: CommitRevertAbortRequest,
ctx: &Session,
) -> Result<(), AppError> {
let repo: repo::Model = self.utils_check_repo_admin(namespace, repo_name, ctx).await?;
let reset_type = request.reset_type.clone();
git_spawn!(repo, domain -> {
domain.commit_revert_abort(reset_type.as_deref().unwrap_or("hard"))
})?;
Ok(())
}
}