373 lines
14 KiB
Rust
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(¶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<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(¶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<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(¶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<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(¶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<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(¶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<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())
|
|
}
|
|
}
|