gitdataai/lib/git/cmd/init.rs
2026-05-30 01:38:40 +08:00

236 lines
7.1 KiB
Rust

use std::path::PathBuf;
use crate::{
bare::GitBare,
errors::{GitError, GitResult},
};
pub struct InitRepositoriesParams {
pub namespace: String,
pub repo_name: String,
pub default_branch: String,
pub description: Option<String>,
pub initialize_with_readme: bool,
pub enable_lfs: bool,
}
pub struct CloneRepoParams {
pub namespace: String,
pub repo_name: String,
pub source_url: String,
}
impl CloneRepoParams {
pub async fn clone_bare(
storage_root: String,
params: CloneRepoParams,
) -> GitResult<String> {
let repo_dir = PathBuf::from(&storage_root)
.join("repo")
.join(&params.namespace)
.join(&params.repo_name);
if repo_dir.exists() {
return Err(GitError::Internal(format!(
"repository directory already exists: {}",
repo_dir.display()
)));
}
if let Some(parent) = repo_dir.parent() {
std::fs::create_dir_all(parent)?;
}
// Clone as bare repo from source URL
let output = duct::cmd(
"git",
&[
"clone",
"--bare",
&params.source_url,
repo_dir.to_string_lossy().as_ref(),
],
)
.stdout_capture()
.stderr_capture()
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_TERMINAL_PROMPT", "0")
.unchecked()
.run()?;
if !output.status.success() {
std::fs::remove_dir_all(&repo_dir).ok();
return Err(GitError::CommandFailed {
status_code: output.status.code(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
});
}
Ok(repo_dir.to_string_lossy().to_string())
}
}
impl InitRepositoriesParams {
pub async fn init_bare(
basic_path: String,
params: InitRepositoriesParams,
) -> GitResult<String> {
let repo_dir = PathBuf::from(&basic_path)
.join("repo")
.join(&params.namespace)
.join(&params.repo_name);
if repo_dir.exists() {
return Err(GitError::Internal(format!(
"repository directory already exists: {}",
repo_dir.display()
)));
}
std::fs::create_dir_all(&repo_dir)?;
let output = duct::cmd("git", &["init", "--bare"])
.dir(&repo_dir)
.stdout_capture()
.stderr_capture()
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_TERMINAL_PROMPT", "0")
.unchecked()
.run()?;
if !output.status.success() {
std::fs::remove_dir_all(&repo_dir).ok();
return Err(GitError::CommandFailed {
status_code: output.status.code(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
});
}
let bare = GitBare {
bare_dir: repo_dir.clone(),
};
let symref_output = bare.git_command_trusted(vec![
"symbolic-ref".to_string(),
"HEAD".to_string(),
format!("refs/heads/{}", params.default_branch),
])?;
if !symref_output.success {
return Err(GitError::CommandFailed {
status_code: symref_output.status_code,
stderr: symref_output.stderr_lossy(),
});
}
if let Some(desc) = &params.description {
let desc_path = repo_dir.join("description");
std::fs::write(&desc_path, desc)?;
}
if params.enable_lfs {
let gitattributes_path = repo_dir.join("info").join("attributes");
if let Some(parent) = gitattributes_path.parent() {
std::fs::create_dir_all(parent)?;
}
let lfs_attributes = "*.psd filter=lfs diff=lfs merge=lfs -text\n\
*.zip filter=lfs diff=lfs merge=lfs -text\n\
*.tar filter=lfs diff=lfs merge=lfs -text\n\
*.gz filter=lfs diff=lfs merge=lfs -text\n\
*.mp4 filter=lfs diff=lfs merge=lfs -text\n\
*.mov filter=lfs diff=lfs merge=lfs -text\n";
std::fs::write(&gitattributes_path, lfs_attributes)?;
}
if params.initialize_with_readme {
init_initial_commit(&bare, &params)?;
}
Ok(repo_dir.to_string_lossy().to_string())
}
}
fn duct_output_to_error(output: &std::process::Output) -> GitError {
GitError::CommandFailed {
status_code: output.status.code(),
stderr: String::from_utf8_lossy(&output.stderr).into_owned(),
}
}
fn init_initial_commit(
bare: &GitBare,
params: &InitRepositoriesParams,
) -> GitResult<()> {
let tmp_dir = bare.bare_dir.with_extension("tmp-init");
let clone_output = duct::cmd(
"git",
&[
"clone",
bare.bare_dir.to_string_lossy().as_ref(),
tmp_dir.to_string_lossy().as_ref(),
],
)
.stdout_capture()
.stderr_capture()
.unchecked()
.run()?;
if !clone_output.status.success() {
return Err(duct_output_to_error(&clone_output));
}
let checkout_output =
duct::cmd("git", &["checkout", "-b", &params.default_branch])
.dir(&tmp_dir)
.stdout_capture()
.stderr_capture()
.unchecked()
.run()?;
if !checkout_output.status.success() {
std::fs::remove_dir_all(&tmp_dir).ok();
return Err(duct_output_to_error(&checkout_output));
}
let readme_content = format!(
"# {}\n\n{}",
params.repo_name,
params.description.as_deref().unwrap_or("")
);
let readme_path = tmp_dir.join("README.md");
std::fs::write(&readme_path, readme_content)?;
let add_output = duct::cmd("git", &["add", "README.md"])
.dir(&tmp_dir)
.stdout_capture()
.stderr_capture()
.unchecked()
.run()?;
if !add_output.status.success() {
std::fs::remove_dir_all(&tmp_dir).ok();
return Err(duct_output_to_error(&add_output));
}
let commit_output = duct::cmd("git", &["commit", "-m", "Initial commit"])
.dir(&tmp_dir)
.stdout_capture()
.stderr_capture()
.env("GIT_CONFIG_NOSYSTEM", "1")
.env("GIT_COMMITTER_NAME", "panda")
.env("GIT_COMMITTER_EMAIL", "panda@gitdata.ai")
.env("GIT_AUTHOR_NAME", "panda")
.env("GIT_AUTHOR_EMAIL", "panda@gitdata.ai")
.unchecked()
.run()?;
if !commit_output.status.success() {
std::fs::remove_dir_all(&tmp_dir).ok();
return Err(duct_output_to_error(&commit_output));
}
let push_output =
duct::cmd("git", &["push", "origin", &params.default_branch])
.dir(&tmp_dir)
.stdout_capture()
.stderr_capture()
.unchecked()
.run()?;
std::fs::remove_dir_all(&tmp_dir).ok();
if !push_output.status.success() {
return Err(duct_output_to_error(&push_output));
}
Ok(())
}