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, 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 { let repo_dir = PathBuf::from(&storage_root) .join("repo") .join(¶ms.namespace) .join(¶ms.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", ¶ms.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 { let repo_dir = PathBuf::from(&basic_path) .join("repo") .join(¶ms.namespace) .join(¶ms.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) = ¶ms.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, ¶ms)?; } 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", ¶ms.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", ¶ms.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(()) }