Files
gitks/service/repo/git/merge.rs
T
2026-06-07 11:30:56 +08:00

373 lines
14 KiB
Rust

use serde::{Deserialize, Serialize};
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
fn rev(r: &str) -> crate::pb::repo::ObjectSelector {
crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: r.to_string(),
},
)),
}
}
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct MergeParams {
pub target_branch: String,
pub source: String,
pub message: Option<String>,
pub squash: Option<bool>,
pub no_commit: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct RebaseParams {
pub branch: String,
pub upstream: String,
}
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct CherryPickParams {
pub commit: String,
pub branch: String,
pub message: Option<String>,
pub mainline: Option<u32>,
}
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct RevertParams {
pub commit: String,
pub branch: String,
pub message: Option<String>,
}
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct CreateCommitParams {
pub branch: String,
pub message: String,
pub start_revision: Option<String>,
pub actions: Vec<CommitAction>,
pub force: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct CommitAction {
pub action: String,
pub file_path: String,
pub previous_path: Option<String>,
pub content: Option<String>,
pub executable: Option<bool>,
}
#[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)]
pub struct CompareParams {
pub base: String,
pub head: String,
pub page_size: Option<u32>,
}
impl RepoService {
pub async fn git_check_merge(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
target: &str,
source: &str,
) -> Result<crate::pb::repo::MergeResult, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_readable(user_uid, &repo).await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let mut svc = self.git_client(&repo)?.merge;
let resp = svc
.check_merge(tonic::Request::new(crate::pb::repo::CheckMergeRequest {
repository: Some(header),
target: Some(rev(target)),
source: Some(rev(source)),
options: None,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_merge(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
params: MergeParams,
) -> Result<crate::pb::repo::MergeResult, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_role_at_least(user_uid, &repo, crate::models::common::Role::Member)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let message = params
.message
.unwrap_or_else(|| format!("Merge {} into {}", params.source, params.target_branch));
let options = crate::pb::repo::MergeOptions {
strategy: crate::pb::repo::merge_options::Strategy::MergeStrategyOrt as i32,
fast_forward:
crate::pb::repo::merge_options::FastForwardMode::MergeFastForwardModeAllowed as i32,
squash: params.squash.unwrap_or(false),
no_commit: params.no_commit.unwrap_or(false),
allow_unrelated_histories: false,
strategy_options: vec![],
};
let mut svc = self.git_client(&repo)?.merge;
let resp = svc
.merge(tonic::Request::new(crate::pb::repo::MergeRequest {
repository: Some(header),
target_branch: params.target_branch,
source: Some(rev(&params.source)),
committer: None,
message,
options: Some(options),
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_rebase(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
params: RebaseParams,
) -> Result<crate::pb::repo::RebaseResult, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_role_at_least(user_uid, &repo, crate::models::common::Role::Member)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let mut svc = self.git_client(&repo)?.merge;
let resp = svc
.rebase(tonic::Request::new(crate::pb::repo::RebaseRequest {
repository: Some(header),
branch: params.branch,
upstream: Some(rev(&params.upstream)),
committer: None,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_cherry_pick(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
params: CherryPickParams,
) -> Result<crate::pb::repo::CreateCommitResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_role_at_least(user_uid, &repo, crate::models::common::Role::Member)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let message = params
.message
.unwrap_or_else(|| format!("Cherry-pick {}", params.commit));
let mut svc = self.git_client(&repo)?.commit;
let resp = svc
.cherry_pick_commit(tonic::Request::new(
crate::pb::repo::CherryPickCommitRequest {
repository: Some(header),
commit: Some(rev(&params.commit)),
branch: params.branch,
committer: None,
message,
mainline: params.mainline.unwrap_or(1),
},
))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_revert(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
params: RevertParams,
) -> Result<crate::pb::repo::CreateCommitResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_role_at_least(user_uid, &repo, crate::models::common::Role::Member)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let message = params
.message
.unwrap_or_else(|| format!("Revert {}", params.commit));
let mut svc = self.git_client(&repo)?.commit;
let resp = svc
.revert_commit(tonic::Request::new(crate::pb::repo::RevertCommitRequest {
repository: Some(header),
commit: Some(rev(&params.commit)),
branch: params.branch,
committer: None,
message,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_create_commit(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
params: CreateCommitParams,
) -> Result<crate::pb::repo::CreateCommitResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_role_at_least(user_uid, &repo, crate::models::common::Role::Member)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let actions: Vec<crate::pb::repo::CreateCommitAction> = params
.actions
.iter()
.map(|a| {
let action = match a.action.as_str() {
"create" => {
crate::pb::repo::create_commit_action::Action::CreateCommitActionCreate
as i32
}
"update" => {
crate::pb::repo::create_commit_action::Action::CreateCommitActionUpdate
as i32
}
"delete" => {
crate::pb::repo::create_commit_action::Action::CreateCommitActionDelete
as i32
}
"move" => {
crate::pb::repo::create_commit_action::Action::CreateCommitActionMove as i32
}
"chmod" => {
crate::pb::repo::create_commit_action::Action::CreateCommitActionChmod
as i32
}
_ => {
crate::pb::repo::create_commit_action::Action::CreateCommitActionUnspecified
as i32
}
};
crate::pb::repo::CreateCommitAction {
action,
file_path: a.file_path.clone(),
previous_path: a.previous_path.clone().unwrap_or_default(),
content: a
.content
.as_ref()
.map(|c| c.as_bytes().to_vec())
.unwrap_or_default(),
encoding: String::new(),
executable: a.executable.unwrap_or(false),
last_commit_oid: None,
}
})
.collect();
let start_revision = params.start_revision.as_ref().map(|r| rev(r));
let mut svc = self.git_client(&repo)?.commit;
let resp = svc
.create_commit(tonic::Request::new(crate::pb::repo::CreateCommitRequest {
repository: Some(header),
branch: params.branch,
message: params.message,
author: None,
committer: None,
actions,
start_revision,
force: params.force.unwrap_or(false),
trailers: vec![],
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_compare_commits(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
params: CompareParams,
) -> Result<crate::pb::repo::CompareCommitsResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_readable(user_uid, &repo).await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let page_size = params.page_size.unwrap_or(30);
let mut svc = self.git_client(&repo)?.commit;
let resp = svc
.compare_commits(tonic::Request::new(
crate::pb::repo::CompareCommitsRequest {
repository: Some(header),
base: Some(rev(&params.base)),
head: Some(rev(&params.head)),
straight: false,
first_parent: false,
pagination: Some(crate::pb::repo::Pagination {
page_size,
page_token: String::new(),
}),
},
))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_list_conflicts(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
target: &str,
source: &str,
page_size: u32,
) -> Result<crate::pb::repo::ListMergeConflictsResponse, AppError> {
let user_uid = ctx.user().ok_or(AppError::Unauthorized)?;
let repo = self.resolve_repo(wk_name, repo_name).await?;
self.ensure_repo_readable(user_uid, &repo).await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let mut svc = self.git_client(&repo)?.merge;
let resp = svc
.list_merge_conflicts(tonic::Request::new(
crate::pb::repo::ListMergeConflictsRequest {
repository: Some(header),
target: Some(rev(target)),
source: Some(rev(source)),
pagination: Some(crate::pb::repo::Pagination {
page_size,
page_token: String::new(),
}),
},
))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
}