236 lines
7.1 KiB
Rust
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(¶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<String> {
|
|
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(())
|
|
}
|