use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
use gitgrip::core::griptree::GriptreeConfig;
use super::git_helpers;
pub struct WorkspaceFixture {
pub _temp: TempDir,
pub workspace_root: PathBuf,
pub remotes_dir: PathBuf,
pub repo_names: Vec<String>,
pub reference_repos: Vec<String>,
}
impl WorkspaceFixture {
pub fn repo_path(&self, name: &str) -> PathBuf {
self.workspace_root.join(name)
}
pub fn remote_path(&self, name: &str) -> PathBuf {
self.remotes_dir.join(format!("{}.git", name))
}
pub fn remote_url(&self, name: &str) -> String {
format!("file://{}", self.remote_path(name).display())
}
pub fn load_manifest(&self) -> gitgrip::core::manifest::Manifest {
let manifest_path =
gitgrip::core::manifest_paths::resolve_gripspace_manifest_path(&self.workspace_root)
.expect("workspace manifest path should resolve");
let content = fs::read_to_string(&manifest_path).unwrap_or_else(|e| {
panic!(
"failed to read manifest at {}: {}",
manifest_path.display(),
e
)
});
gitgrip::core::manifest::Manifest::parse(&content).expect("failed to parse manifest")
}
}
pub fn write_griptree_config(workspace_root: &Path, branch: &str, repo: &str, upstream: &str) {
let mut config = GriptreeConfig::new(branch, &workspace_root.to_string_lossy());
config
.repo_upstreams
.insert(repo.to_string(), upstream.to_string());
let config_path = workspace_root.join(".gitgrip").join("griptree.json");
config.save(&config_path).unwrap();
}
pub struct WorkspaceBuilder {
repos: Vec<RepoSpec>,
with_manifest_repo: bool,
}
struct RepoSpec {
name: String,
reference: bool,
with_remote: bool,
files: Vec<(String, String)>,
groups: Vec<String>,
}
impl WorkspaceBuilder {
pub fn new() -> Self {
Self {
repos: Vec::new(),
with_manifest_repo: false,
}
}
pub fn add_repo(mut self, name: &str) -> Self {
self.repos.push(RepoSpec {
name: name.to_string(),
reference: false,
with_remote: true,
files: vec![("README.md".to_string(), format!("# {}\n", name))],
groups: Vec::new(),
});
self
}
pub fn add_repo_with_groups(mut self, name: &str, groups: Vec<&str>) -> Self {
self.repos.push(RepoSpec {
name: name.to_string(),
reference: false,
with_remote: true,
files: vec![("README.md".to_string(), format!("# {}\n", name))],
groups: groups.into_iter().map(|g| g.to_string()).collect(),
});
self
}
pub fn add_reference_repo(mut self, name: &str) -> Self {
self.repos.push(RepoSpec {
name: name.to_string(),
reference: true,
with_remote: true,
files: vec![("README.md".to_string(), format!("# {} (reference)\n", name))],
groups: Vec::new(),
});
self
}
pub fn add_repo_with_files(mut self, name: &str, files: Vec<(&str, &str)>) -> Self {
self.repos.push(RepoSpec {
name: name.to_string(),
reference: false,
with_remote: true,
files: files
.into_iter()
.map(|(n, c)| (n.to_string(), c.to_string()))
.collect(),
groups: Vec::new(),
});
self
}
pub fn with_manifest_repo(mut self) -> Self {
self.with_manifest_repo = true;
self
}
pub fn build(self) -> WorkspaceFixture {
let temp = TempDir::new().expect("failed to create temp dir");
let workspace_root = temp.path().join("workspace");
let remotes_dir = temp.path().join("remotes");
fs::create_dir_all(&workspace_root).unwrap();
fs::create_dir_all(&remotes_dir).unwrap();
let mut repo_names = Vec::new();
let mut reference_repos = Vec::new();
for spec in &self.repos {
let bare_path = remotes_dir.join(format!("{}.git", spec.name));
let repo_path = workspace_root.join(&spec.name);
git_helpers::init_bare_repo(&bare_path);
let staging = temp.path().join(format!("staging-{}", spec.name));
git_helpers::init_repo(&staging);
for (filename, content) in &spec.files {
git_helpers::commit_file(&staging, filename, content, &format!("Add {}", filename));
}
let remote_url = format!("file://{}", bare_path.display());
git_helpers::add_remote(&staging, "origin", &remote_url);
git_helpers::push_upstream(&staging, "origin", "main");
git_helpers::clone_repo(&remote_url, &repo_path);
if spec.reference {
reference_repos.push(spec.name.clone());
}
repo_names.push(spec.name.clone());
}
let manifest_yaml = generate_manifest(&self.repos, &remotes_dir);
let manifest_dir = workspace_root.join(".gitgrip").join("spaces").join("main");
fs::create_dir_all(&manifest_dir).unwrap();
fs::write(manifest_dir.join("gripspace.yml"), &manifest_yaml).unwrap();
let legacy_manifest_dir = workspace_root.join(".gitgrip").join("manifests");
fs::create_dir_all(&legacy_manifest_dir).unwrap();
fs::write(legacy_manifest_dir.join("manifest.yaml"), &manifest_yaml).unwrap();
fs::create_dir_all(workspace_root.join(".gitgrip").join("spaces").join("local")).unwrap();
if self.with_manifest_repo {
git_helpers::init_repo(&manifest_dir);
git_helpers::commit_file(
&manifest_dir,
"gripspace.yml",
&manifest_yaml,
"Initial manifest",
);
}
WorkspaceFixture {
_temp: temp,
workspace_root,
remotes_dir,
repo_names,
reference_repos,
}
}
}
fn generate_manifest(repos: &[RepoSpec], remotes_dir: &Path) -> String {
let mut yaml = String::from("version: 1\nrepos:\n");
for spec in repos {
let remote_url = format!(
"file://{}",
remotes_dir.join(format!("{}.git", spec.name)).display()
);
yaml.push_str(&format!(" {}:\n", spec.name));
yaml.push_str(&format!(" url: {}\n", remote_url));
yaml.push_str(&format!(" path: {}\n", spec.name));
yaml.push_str(" default_branch: main\n");
if spec.reference {
yaml.push_str(" reference: true\n");
}
if !spec.groups.is_empty() {
let groups_str: Vec<String> =
spec.groups.iter().map(|g| format!("\"{}\"", g)).collect();
yaml.push_str(&format!(" groups: [{}]\n", groups_str.join(", ")));
}
}
yaml
}