feat: init

This commit is contained in:
zhenyi
2026-06-07 11:30:56 +08:00
commit 563381c1ca
361 changed files with 41327 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
impl RepoService {
pub async fn git_blame(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
revision: &str,
path: &str,
page_size: u32,
) -> Result<crate::pb::repo::BlameResponse, 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)?.blame;
let resp = svc
.blame(tonic::Request::new(crate::pb::repo::BlameRequest {
repository: Some(header),
revision: Some(crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: revision.to_string(),
},
)),
}),
path: path.to_string(),
range: None,
options: None,
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())
}
}
+142
View File
@@ -0,0 +1,142 @@
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
impl RepoService {
pub async fn git_list_branches(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
pattern: Option<String>,
page_size: u32,
page_token: Option<String>,
) -> Result<crate::pb::repo::ListBranchesResponse, 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)?.branch;
let resp = svc
.list_branches(tonic::Request::new(crate::pb::repo::ListBranchesRequest {
repository: Some(header),
pattern: pattern.unwrap_or_default(),
merged_into_head: false,
not_merged_into_head: false,
pagination: Some(crate::pb::repo::Pagination {
page_size,
page_token: page_token.unwrap_or_default(),
}),
sort_direction: crate::pb::repo::SortDirection::Desc as i32,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_get_branch(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
branch_name: &str,
) -> Result<crate::pb::repo::Branch, 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)?.branch;
let resp = svc
.get_branch(tonic::Request::new(crate::pb::repo::GetBranchRequest {
repository: Some(header),
name: branch_name.to_string(),
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_create_branch(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
branch_name: &str,
start_point: &str,
) -> Result<crate::pb::repo::Branch, 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)?.branch;
let resp = svc
.create_branch(tonic::Request::new(crate::pb::repo::CreateBranchRequest {
repository: Some(header),
name: branch_name.to_string(),
start_point: Some(crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: start_point.to_string(),
},
)),
}),
force: false,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_delete_branch(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
branch_name: &str,
) -> Result<(), 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::Admin)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let mut svc = self.git_client(&repo)?.branch;
svc.delete_branch(tonic::Request::new(crate::pb::repo::DeleteBranchRequest {
repository: Some(header),
name: branch_name.to_string(),
force: false,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(())
}
pub async fn git_compare_branches(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
source_branch: &str,
target_branch: &str,
) -> Result<crate::pb::repo::CompareBranchResponse, 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)?.branch;
let resp = svc
.compare_branch(tonic::Request::new(crate::pb::repo::CompareBranchRequest {
repository: Some(header),
source_branch: source_branch.to_string(),
target_branch: target_branch.to_string(),
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
}
+79
View File
@@ -0,0 +1,79 @@
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
impl RepoService {
pub async fn git_list_commits(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
revision: &str,
path: Option<String>,
page_size: u32,
) -> Result<crate::pb::repo::ListCommitsResponse, 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)?.commit;
let resp = svc
.list_commits(tonic::Request::new(crate::pb::repo::ListCommitsRequest {
repository: Some(header),
revision: Some(crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: revision.to_string(),
},
)),
}),
path: path.unwrap_or_default(),
since: None,
until: None,
first_parent: false,
all: false,
reverse: false,
max_parents: 0,
min_parents: 0,
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_get_commit(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
revision: &str,
) -> Result<crate::pb::repo::Commit, 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)?.commit;
let resp = svc
.get_commit(tonic::Request::new(crate::pb::repo::GetCommitRequest {
repository: Some(header),
revision: Some(crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: revision.to_string(),
},
)),
}),
include_stats: true,
include_raw: false,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
}
+72
View File
@@ -0,0 +1,72 @@
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
fn rev(revision: &str) -> crate::pb::repo::ObjectSelector {
crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: revision.to_string(),
},
)),
}
}
impl RepoService {
pub async fn git_diff(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
base: &str,
head: &str,
page_size: u32,
) -> Result<crate::pb::repo::GetDiffResponse, 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)?.diff;
let resp = svc
.get_diff(tonic::Request::new(crate::pb::repo::GetDiffRequest {
repository: Some(header),
base: Some(rev(base)),
head: Some(rev(head)),
options: None,
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_diff_stats(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
base: &str,
head: &str,
) -> Result<crate::pb::repo::DiffStats, 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)?.diff;
let resp = svc
.get_diff_stats(tonic::Request::new(crate::pb::repo::GetDiffStatsRequest {
repository: Some(header),
base: Some(rev(base)),
head: Some(rev(head)),
options: None,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
}
+372
View File
@@ -0,0 +1,372 @@
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())
}
}
+32
View File
@@ -0,0 +1,32 @@
pub mod blame;
pub mod branch;
pub mod commit;
pub mod diff;
pub mod merge;
pub mod repository;
pub mod tag;
pub mod tree;
use crate::error::AppError;
use crate::models::repos::Repo;
use crate::models::workspaces::Workspace;
use crate::pb::RepoClient;
use crate::pb::repo::RepositoryHeader;
use crate::service::RepoService;
impl RepoService {
pub(crate) fn repo_header(&self, repo: &Repo, ws: &Workspace) -> RepositoryHeader {
RepositoryHeader {
storage_name: ws.name.clone(),
relative_path: format!("{}.git", repo.name),
storage_path: repo.storage_path.clone(),
}
}
pub(crate) fn git_client(&self, repo: &Repo) -> Result<RepoClient, AppError> {
self.ctx
.registry
.get_git_client(&repo.primary_storage_node_id)
.ok_or_else(|| AppError::Config("primary git node not available".into()))
}
}
+123
View File
@@ -0,0 +1,123 @@
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
impl RepoService {
pub async fn git_repo_info(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
) -> Result<crate::pb::repo::Repository, 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)?.repository;
let resp = svc
.get_repository(tonic::Request::new(crate::pb::repo::GetRepositoryRequest {
repository: Some(header),
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_repo_exists(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
) -> Result<bool, 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)?.repository;
let resp = svc
.repository_exists(tonic::Request::new(
crate::pb::repo::RepositoryExistsRequest {
repository: Some(header),
},
))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner().exists)
}
pub async fn git_repo_stats(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
) -> Result<crate::pb::repo::RepositoryStatistics, 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)?.repository;
let resp = svc
.get_repository_statistics(tonic::Request::new(
crate::pb::repo::RepositoryStatisticsRequest {
repository: Some(header),
},
))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_repo_health(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
) -> Result<crate::pb::repo::RepositoryHealthResponse, 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::Admin)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let mut svc = self.git_client(&repo)?.repository;
let resp = svc
.check_repository_health(tonic::Request::new(
crate::pb::repo::RepositoryHealthRequest {
repository: Some(header),
connectivity_only: false,
},
))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_garbage_collect(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
) -> Result<crate::pb::repo::RepositoryMaintenanceResponse, 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::Admin)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let mut svc = self.git_client(&repo)?.repository;
let resp = svc
.garbage_collect(tonic::Request::new(
crate::pb::repo::GarbageCollectRequest {
repository: Some(header),
prune: true,
aggressive: false,
},
))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
}
+96
View File
@@ -0,0 +1,96 @@
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
impl RepoService {
pub async fn git_list_tags(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
pattern: Option<String>,
page_size: u32,
) -> Result<crate::pb::repo::ListTagsResponse, 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)?.tag;
let resp = svc
.list_tags(tonic::Request::new(crate::pb::repo::ListTagsRequest {
repository: Some(header),
pattern: pattern.unwrap_or_default(),
pagination: Some(crate::pb::repo::Pagination {
page_size,
page_token: String::new(),
}),
sort_direction: crate::pb::repo::SortDirection::Desc as i32,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
#[allow(clippy::too_many_arguments)]
pub async fn git_create_tag(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
tag_name: &str,
target: &str,
message: Option<String>,
annotated: bool,
) -> Result<crate::pb::repo::Tag, 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)?.tag;
let resp = svc
.create_tag(tonic::Request::new(crate::pb::repo::CreateTagRequest {
repository: Some(header),
name: tag_name.to_string(),
target: Some(crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: target.to_string(),
},
)),
}),
message: message.unwrap_or_default(),
tagger: None,
force: false,
annotated,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
pub async fn git_delete_tag(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
tag_name: &str,
) -> Result<(), 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::Admin)
.await?;
let ws = self.resolve_workspace(wk_name).await?;
let header = self.repo_header(&repo, &ws);
let mut svc = self.git_client(&repo)?.tag;
svc.delete_tag(tonic::Request::new(crate::pb::repo::DeleteTagRequest {
repository: Some(header),
name: tag_name.to_string(),
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(())
}
}
+77
View File
@@ -0,0 +1,77 @@
use crate::error::AppError;
use crate::service::RepoService;
use crate::session::Session;
impl RepoService {
#[allow(clippy::too_many_arguments)]
pub async fn git_list_tree(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
revision: &str,
path: Option<String>,
recursive: bool,
page_size: u32,
) -> Result<crate::pb::repo::ListTreeResponse, 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)?.tree;
let resp = svc
.list_tree(tonic::Request::new(crate::pb::repo::ListTreeRequest {
repository: Some(header),
revision: Some(crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: revision.to_string(),
},
)),
}),
path: path.unwrap_or_default(),
recursive,
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_get_blob(
&self,
ctx: &Session,
wk_name: &str,
repo_name: &str,
revision: &str,
path: &str,
) -> Result<crate::pb::repo::Blob, 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)?.tree;
let resp = svc
.get_blob(tonic::Request::new(crate::pb::repo::GetBlobRequest {
repository: Some(header),
revision: Some(crate::pb::repo::ObjectSelector {
selector: Some(crate::pb::repo::object_selector::Selector::Revision(
crate::pb::repo::ObjectName {
revision: revision.to_string(),
},
)),
}),
path: path.to_string(),
oid: None,
max_bytes: 0,
}))
.await
.map_err(|e| AppError::InternalServerError(e.to_string()))?;
Ok(resp.into_inner())
}
}