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, pub squash: Option, pub no_commit: Option, } #[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, pub mainline: Option, } #[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)] pub struct RevertParams { pub commit: String, pub branch: String, pub message: Option, } #[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)] pub struct CreateCommitParams { pub branch: String, pub message: String, pub start_revision: Option, pub actions: Vec, pub force: Option, } #[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)] pub struct CommitAction { pub action: String, pub file_path: String, pub previous_path: Option, pub content: Option, pub executable: Option, } #[derive(Debug, Deserialize, Serialize, utoipa::ToSchema)] pub struct CompareParams { pub base: String, pub head: String, pub page_size: Option, } impl RepoService { pub async fn git_check_merge( &self, ctx: &Session, wk_name: &str, repo_name: &str, target: &str, source: &str, ) -> Result { 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 { 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(¶ms.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 { 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(¶ms.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 { 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(¶ms.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 { 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(¶ms.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 { 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 = 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 { 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(¶ms.base)), head: Some(rev(¶ms.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 { 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()) } }