1098df8
ZhenYi
refactor: streamline context handling by replacing space and repo name parameters with context references for improved clarity and consistency
1098df8
use proto::kenshell::v1 as pb;
use serde::{Deserialize, Serialize};
use crate::context::facade::GitContext;
use crate::service::GitService;
use crate::service::rpc::{rpc_error, wait_job};
use crate::share::errors::GitError;
use crate::share::validate::{validate_refspec, validate_remote_name, validate_remote_url};
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ImportResultDto {
pub default_branch: String,
pub commit_count: i64,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct FetchResultDto {
pub updated: Vec<String>,
pub new: Vec<String>,
}
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct PushResultDto {
pub pushed: Vec<String>,
pub rejected: Vec<String>,
}
#[derive(Debug, Clone, Deserialize, utoipa::ToSchema)]
pub struct RemoteAuthDto {
#[serde(default)]
pub token: Option<String>,
#[serde(default)]
pub username: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RemoteConfigDto {
pub url: String,
#[serde(default)]
pub auth: Option<RemoteAuthDto>,
#[serde(default)]
pub name: Option<String>,
#[serde(default)]
pub refspec: Option<String>,
}
#[derive(Debug, Clone, serde::Serialize, utoipa::ToSchema)]
pub struct RemoteDto {
pub name: String,
pub fetch_url: Option<String>,
pub push_url: Option<String>,
}
fn remote_auth(auth: Option<&RemoteAuthDto>) -> Option<pb::RemoteAuth> {
let auth = auth?;
if auth.token.as_deref().unwrap_or("").is_empty() {
return None;
}
Some(pb::RemoteAuth {
username: auth.username.clone().unwrap_or_else(|| "git".to_string()),
token: auth.token.clone().unwrap_or_default(),
authorization_header: String::new(),
ssh_key: String::new(),
known_hosts: String::new(),
resolved_address: String::new(),
})
}
impl GitService {
pub async fn remote_clone_import(
&self,
ctx: &GitContext,
url: String,
auth: Option<RemoteAuthDto>,
) -> Result<ImportResultDto, GitError> {
validate_remote_url(&url)?;
let mut client = ctx.ks.remote();
let job = client
.start_remote_import(pb::StartRemoteImportRequest {
repository: Some(ctx.repository_ref()),
url,
auth: remote_auth(auth.as_ref()),
actor: Some(ctx.actor()),
idempotency_key: uuid::Uuid::now_v7().to_string(),
})
.await
.map_err(rpc_error)?
.into_inner()
.job
.ok_or_else(|| GitError::Internal("import job missing".into()))?;
let job = wait_job(&ctx.ks, &job.id, self.job_timeout()).await?;
match job.result {
Some(pb::job::Result::Remote(remote)) => Ok(ImportResultDto {
default_branch: remote.default_branch,
commit_count: remote.commit_count as i64,
}),
_ => Err(GitError::Internal("import job returned no result".into())),
}
}
pub async fn remote_fetch(
&self,
ctx: &GitContext,
remote: String,
auth: Option<RemoteAuthDto>,
) -> Result<FetchResultDto, GitError> {
validate_remote_name(&remote)?;
let mut client = ctx.ks.remote();
let job = client
.start_remote_fetch(pb::StartRemoteFetchRequest {
repository: Some(ctx.repository_ref()),
remote,
auth: remote_auth(auth.as_ref()),
actor: Some(ctx.actor()),
idempotency_key: uuid::Uuid::now_v7().to_string(),
})
.await
.map_err(rpc_error)?
.into_inner()
.job
.ok_or_else(|| GitError::Internal("fetch job missing".into()))?;
let job = wait_job(&ctx.ks, &job.id, self.job_timeout()).await?;
match job.result {
Some(pb::job::Result::Remote(remote)) => Ok(FetchResultDto {
updated: remote.updated,
new: remote.new_refs,
}),
_ => Err(GitError::Internal("fetch job returned no result".into())),
}
}
pub async fn remote_push(
&self,
ctx: &GitContext,
remote: String,
refspec: Option<String>,
auth: Option<RemoteAuthDto>,
) -> Result<PushResultDto, GitError> {
validate_remote_name(&remote)?;
if let Some(refspec) = refspec.as_deref() {
validate_refspec(refspec)?;
}
let mut client = ctx.ks.remote();
let job = client
.start_remote_push(pb::StartRemotePushRequest {
repository: Some(ctx.repository_ref()),
remote,
refspec: refspec.unwrap_or_default(),
force: false,
auth: remote_auth(auth.as_ref()),
actor: Some(ctx.actor()),
idempotency_key: uuid::Uuid::now_v7().to_string(),
})
.await
.map_err(rpc_error)?
.into_inner()
.job
.ok_or_else(|| GitError::Internal("push job missing".into()))?;
let job = wait_job(&ctx.ks, &job.id, self.job_timeout()).await?;
match job.result {
Some(pb::job::Result::Remote(remote)) => Ok(PushResultDto {
pushed: remote.pushed,
rejected: remote.rejected,
}),
_ => Err(GitError::Internal("push job returned no result".into())),
}
}
pub async fn remote_add(
&self,
ctx: &GitContext,
name: String,
url: String,
) -> Result<(), GitError> {
validate_remote_name(&name)?;
validate_remote_url(&url)?;
let mut client = ctx.ks.remote();
client
.upsert_remote(pb::UpsertRemoteRequest {
repository: Some(ctx.repository_ref()),
name,
url,
actor: Some(ctx.actor()),
options: Some(ctx.write_options()),
})
.await
.map_err(rpc_error)?;
Ok(())
}
pub async fn remote_upsert(
&self,
ctx: &GitContext,
name: String,
url: String,
) -> Result<(), GitError> {
self.remote_add(ctx, name, url).await
}
pub async fn remote_list(&self, ctx: &GitContext) -> Result<Vec<RemoteDto>, GitError> {
let mut client = ctx.ks.remote();
let response = client
.list_remotes(pb::ListRemotesRequest {
repository: Some(ctx.repository_ref()),
})
.await
.map_err(rpc_error)?
.into_inner();
let mut rows: Vec<RemoteDto> = response
.remotes
.into_iter()
.map(|remote| RemoteDto {
name: remote.name,
fetch_url: Some(remote.fetch_url).filter(|url| !url.is_empty()),
push_url: Some(remote.push_url).filter(|url| !url.is_empty()),
})
.collect();
rows.sort_by(|a, b| a.name.cmp(&b.name));
Ok(rows)
}
pub async fn remote_remove(&self, ctx: &GitContext, name: String) -> Result<(), GitError> {
validate_remote_name(&name)?;
let mut client = ctx.ks.remote();
client
.remove_remote(pb::RemoveRemoteRequest {
repository: Some(ctx.repository_ref()),
name,
actor: Some(ctx.actor()),
options: Some(ctx.write_options()),
})
.await
.map_err(rpc_error)?;
Ok(())
}
}