Skip to content
Clone
971c37c
192 lines 5.8 KB Rust
use std::path::PathBuf;
use export::storage::AppStorage;
use gitbaby::archive::types::{ArchiveFormat, CreateArchiveRequest};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use crate::service::GitService;
use crate::service::cache::sanitize_key;
use crate::service::gitcmd::{run_git, run_git_capped};
use crate::share::errors::{GitError, map_baby};
use crate::share::validate::validate_revision;
#[derive(Debug, Clone, Serialize, utoipa::ToSchema)]
pub struct ArchiveResultDto {
pub format: String,
pub size: i64,
pub cache_key: String,
pub download_url: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct ArchiveOptionsDto {
pub format: ArchiveFormat,
#[serde(default)]
pub path_filter: Option<String>,
}
fn fmt_ext(f: ArchiveFormat) -> &'static str {
match f {
ArchiveFormat::Tar => "tar",
ArchiveFormat::TarGz => "tar.gz",
ArchiveFormat::TarBz2 => "tar.bz2",
ArchiveFormat::Zip => "zip",
}
}
impl GitService {
pub async fn archive_create(
&self,
space_name: String,
repo_name: String,
revision: String,
opts: ArchiveOptionsDto,
) -> Result<ArchiveResultDto, GitError> {
validate_revision(&revision)?;
let ctx = self.context(space_name, repo_name).await?;
let baby = self.baby(&ctx);
let ext = fmt_ext(opts.format);
let prefix_filter = opts.path_filter.clone();
let req = CreateArchiveRequest {
commit: revision.clone(),
format: opts.format,
prefix: Some(format!("{}-", ctx.model.id.simple())),
paths: prefix_filter.into_iter().collect(),
};
baby.create_archive(req).await.map_err(map_baby)?;
let key = format!(
"git/{}/{}.{}",
ctx.model.id.simple(),
sanitize_key(&revision),
ext
);
let out_dir = ctx.repo_dir();
let storage: &AppStorage = &self.state.storage;
let timeout = self.state.config.git.git_command_timeout_secs;
let bytes = read_archive_bytes(&out_dir, &revision, ext, timeout).await?;
let bytes_len = bytes.len() as i64;
storage
.client()
.put(&key, &bytes)
.await
.map_err(|e| GitError::Internal(format!("archive put failed: {e}")))?;
let download_url = storage.client().presign_get(&key, 3600).ok();
Ok(ArchiveResultDto {
format: ext.to_string(),
size: bytes_len,
cache_key: key,
download_url,
})
}
pub async fn archive_download(
&self,
space_name: String,
repo_name: String,
revision: String,
opts: ArchiveOptionsDto,
) -> Result<ArchiveResultDto, GitError> {
validate_revision(&revision)?;
let ctx = self.context(space_name, repo_name).await?;
let ext = fmt_ext(opts.format);
let key = format!(
"git/{}/{}.{}",
ctx.model.id.simple(),
sanitize_key(&revision),
ext
);
let storage: &AppStorage = &self.state.storage;
let head = storage
.client()
.head(&key)
.await
.map_err(|e| GitError::Internal(format!("storage head: {e}")))?;
let Some((size, _)) = head else {
return Err(GitError::NotFound);
};
let download_url = storage.client().presign_get(&key, 3600).ok();
Ok(ArchiveResultDto {
format: ext.to_string(),
size: size as i64,
cache_key: key,
download_url,
})
}
pub async fn archive_create_bundle(
&self,
space_name: String,
repo_name: String,
) -> Result<ArchiveResultDto, GitError> {
let ctx = self.context(space_name, repo_name).await?;
let bundle_path = std::env::temp_dir().join(format!(
"gitdata-bundle-{}-{}",
std::process::id(),
OffsetDateTime::now_utc().unix_timestamp_nanos()
));
let timeout = self.state.config.git.git_command_timeout_secs;
run_git(
"archives::bundle",
&ctx.repo_dir(),
vec![
"bundle".to_string(),
"create".to_string(),
bundle_path.to_string_lossy().into_owned(),
"--all".to_string(),
],
&[],
timeout,
)
.await?;
let key = format!("git/{}/bundle.git", ctx.model.id.simple());
let storage: &AppStorage = &self.state.storage;
let bytes = tokio::task::spawn_blocking({
let p = bundle_path.clone();
move || std::fs::read(&p)
})
.await
.map_err(|e| GitError::Internal(format!("bundle read task: {e}")))?
.map_err(|e| GitError::Internal(format!("bundle read: {e}")))?;
let bytes_len = bytes.len() as i64;
storage
.client()
.put(&key, &bytes)
.await
.map_err(|e| GitError::Internal(format!("bundle put failed: {e}")))?;
let download_url = storage.client().presign_get(&key, 3600).ok();
let _ = std::fs::remove_file(&bundle_path);
Ok(ArchiveResultDto {
format: "bundle".to_string(),
size: bytes_len,
cache_key: key,
download_url,
})
}
}
async fn read_archive_bytes(
dir: &PathBuf,
rev: &str,
ext: &str,
timeout_secs: u64,
) -> Result<Vec<u8>, GitError> {
let cap = 512 * 1024 * 1024;
let out = run_git_capped(
"archives::read",
dir,
vec![
"archive".to_string(),
"--format".to_string(),
ext.to_string(),
rev.to_string(),
],
&[],
timeout_secs,
cap,
)
.await?;
Ok(out.stdout)
}