use std::fs;
use std::path::{Path, PathBuf};
use argonauts_core::create_private_dir_all;
use super::files::{nonce, remove_if_present};
use crate::error::{LearningError, Result};
use crate::git;
const TEMP_REF_PREFIX: &str = "refs/argonauts/replay";
const BRANCH: &str = "main";
const NO_REFLOG: &str = "core.logAllRefUpdates=false";
pub fn isolated_repo(source: &Path, base: &str, into: &Path) -> Result<PathBuf> {
if base.starts_with('-') {
return Err(LearningError::Refused(format!("{base:?} isn't a commit")));
}
let commit = git::run(
source,
&[
"rev-parse",
"--verify",
"--quiet",
&format!("{base}^{{commit}}"),
],
)
.map_err(|_| {
LearningError::Refused(format!("{base} isn't a commit in {}", source.display()))
})?;
if into.exists() && fs::read_dir(into)?.next().is_some() {
return Err(LearningError::Refused(format!(
"{} already has files in it",
into.display()
)));
}
let parent = into.parent().ok_or_else(|| {
LearningError::Refused(format!("{} has no parent folder", into.display()))
})?;
create_private_dir_all(parent)?;
let id = nonce();
let temp_ref = format!("{TEMP_REF_PREFIX}/{id}");
let bundle = parent.join(format!(".{id}.bundle"));
git::run(source, &["update-ref", &temp_ref, &commit])?;
let built = build(source, parent, &temp_ref, &bundle, into);
let removed = git::run(source, &["update-ref", "-d", &temp_ref]);
let _ = fs::remove_file(&bundle);
if built.is_err() || removed.is_err() {
let _ = fs::remove_dir_all(into);
}
built?;
removed?;
Ok(into.to_path_buf())
}
fn build(source: &Path, parent: &Path, temp_ref: &str, bundle: &Path, into: &Path) -> Result<()> {
let bundle_text = bundle.to_string_lossy();
let into_text = into.to_string_lossy();
git::run(source, &["bundle", "create", &bundle_text, temp_ref])?;
git::run(parent, &["init", "--quiet", "-b", BRANCH, &into_text])?;
git::run(
into,
&[
"-c",
NO_REFLOG,
"fetch",
"--quiet",
"--no-tags",
"--update-head-ok",
&bundle_text,
&format!("{temp_ref}:refs/heads/{BRANCH}"),
],
)?;
git::run(
into,
&["-c", NO_REFLOG, "checkout", "--quiet", "--force", BRANCH],
)?;
remove_if_present(&into.join(".git").join("FETCH_HEAD"))
}