use std::collections::BTreeMap;
use std::io::Write;
use std::path::{Path, PathBuf};
use std::process::{Command, Stdio};
use onevcs::registry::{Checkout, Identity, Registry};
use onevcs::{testing, Error, Result};
pub const IDENTITIES: usize = 20;
pub const TERMS_PER_IDENTITY: usize = 100;
pub const PATHS: usize = 1000;
pub const BYTES: usize = 10 * 1024 * 1024;
pub const DESTINATION: &str = "github.com/sample-owner/openwidget";
pub const PUBLICATION_BRANCH: &str = "generic-workload";
pub const EXPORT_BRANCH: &str = "generic-examples";
pub const EXPORT_DIRECTORY: &str = "examples";
#[derive(Debug)]
pub struct Workload {
pub root: PathBuf,
pub home: PathBuf,
pub destination: PathBuf,
pub destination_origin: PathBuf,
pub source: String,
pub identities: Vec<String>,
pub bytes: usize,
}
pub fn identity(index: usize) -> String {
format!("github.com/shelterco{index:02}/quietvault{index:02}")
}
pub fn build(root: &Path) -> Result<Workload> {
if root.exists() && std::fs::read_dir(root).map_err(io)?.next().is_some() {
return Err(invalid(format!(
"workload root {} must be empty",
root.display()
)));
}
std::fs::create_dir_all(root).map_err(io)?;
let root = std::fs::canonicalize(root).map_err(io)?;
let home = root.join("home");
std::fs::create_dir_all(&home).map_err(io)?;
std::fs::write(
root.join(".gitconfig"),
"[user]\nname=Fixture\nemail=fixture@example.invalid\n[commit]\ngpgsign=false\n\
[maintenance]\nauto=false\n[init]\ndefaultBranch=main\n",
)
.map_err(io)?;
let rules = home.join("rules.yml");
std::fs::write(
&rules,
"version: 4\nrules:\n - match: {host: github.com, owner: \"shelterco*\"}\n \
visibility: private\n - match: {host: github.com, owner: sample-owner, name: \
openwidget}\n visibility: public\ndefault: {publication: local-direct, approvals: \
none}\n",
)
.map_err(io)?;
let files = workload_files();
let bytes = files.iter().map(|(_, body)| body.len()).sum();
let mut registry = Registry {
version: testing::registry_version(),
identities: BTreeMap::new(),
checkouts: BTreeMap::new(),
rules: Some(rules),
};
let mut identities = Vec::new();
for index in 0..IDENTITIES {
let key = identity(index);
let checkout = private_repository(&root, index, &files)?;
registry
.identities
.insert(key.clone(), Identity::new(key.clone(), "<no-op>"));
registry.checkouts.insert(
format!("quietvault{index:02}"),
Checkout {
path: checkout,
identity: key.clone(),
},
);
identities.push(key);
}
let (destination, destination_origin) = public_repository(&root, &files)?;
registry.identities.insert(
DESTINATION.to_owned(),
Identity::new(DESTINATION, "<no-op>"),
);
registry.checkouts.insert(
"openwidget".to_owned(),
Checkout {
path: destination.clone(),
identity: DESTINATION.to_owned(),
},
);
testing::write_registry(&home, ®istry)?;
Ok(Workload {
root,
home,
destination,
destination_origin,
source: identity(0),
identities,
bytes,
})
}
const WORDS: [&str; 32] = [
"amber", "basin", "cedar", "delta", "ember", "fable", "grove", "hazel", "inlet", "jasper",
"kettle", "lumen", "maple", "nectar", "orbit", "pebble", "quartz", "ripple", "sable",
"thistle", "umber", "velvet", "willow", "xenon", "yarrow", "zephyr", "anchor", "bramble",
"canyon", "drift", "estuary", "fern",
];
fn workload_files() -> Vec<(String, String)> {
let mut state: u64 = 0x5eed_b0a7;
let per_file = BYTES / PATHS;
let mut remainder = BYTES % PATHS;
(0..PATHS)
.map(|index| {
let mut size = per_file;
if remainder > 0 {
size += 1;
remainder -= 1;
}
let mut text = String::with_capacity(size + 64);
let mut line = 0;
while text.len() < size {
line += 1;
text.push_str(&format!("line {line}:"));
for _ in 0..9 {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
text.push(' ');
text.push_str(WORDS[(state >> 59) as usize]);
}
text.push('\n');
}
text.truncate(size - 1);
text.push('\n');
(format!("part-{:02}/file-{index:04}.txt", index % 25), text)
})
.collect()
}
fn private_repository(root: &Path, index: usize, files: &[(String, String)]) -> Result<PathBuf> {
let name = format!("quietvault{index:02}");
let repo = root.join("checkouts").join(&name);
std::fs::create_dir_all(&repo).map_err(io)?;
git(&repo, &["init", "-q", "-b", "main"])?;
let declared: Vec<String> = (0..TERMS_PER_IDENTITY - 4)
.map(|term| format!("\"{name}x{term:03}\""))
.collect();
let base = vec![
("README.md".to_owned(), "# notes\n".to_owned()),
(
"Cargo.toml".to_owned(),
format!("[package]\nname = \"{name}-core\"\n"),
),
(
"private-terms.toml".to_owned(),
format!("schema_version = 1\nterms = [{}]\n", declared.join(", ")),
),
];
let mut import = Import::default();
let main = import.commit("main", None, "chore: the repository", &base);
if index == 0 {
let examples: Vec<(String, String)> = files
.iter()
.map(|(path, body)| (format!("{EXPORT_DIRECTORY}/{path}"), body.clone()))
.collect();
import.commit(
EXPORT_BRANCH,
Some(main),
"docs: add generic examples",
&examples,
);
}
import.run(&repo)?;
git(&repo, &["checkout", "-q", "main"])?;
if index == 0 {
let origin = root.join("origins").join(format!("{name}.git"));
git(
root,
&[
"init",
"-q",
"--bare",
"-b",
"main",
&origin.to_string_lossy(),
],
)?;
git(
&repo,
&["remote", "add", "origin", &origin.to_string_lossy()],
)?;
git(&repo, &["push", "-q", "origin", "main"])?;
git(&repo, &["fetch", "-q", "origin"])?;
git(&repo, &["remote", "set-head", "origin", "main"])?;
}
Ok(repo)
}
fn public_repository(root: &Path, files: &[(String, String)]) -> Result<(PathBuf, PathBuf)> {
let origin = root.join("origins/openwidget.git");
let repo = root.join("checkouts/openwidget");
std::fs::create_dir_all(&repo).map_err(io)?;
git(
root,
&[
"init",
"-q",
"--bare",
"-b",
"main",
&origin.to_string_lossy(),
],
)?;
git(&repo, &["init", "-q", "-b", "main"])?;
let mut import = Import::default();
let main = import.commit(
"main",
None,
"chore: seed the repository",
&[("README.md".to_owned(), "# openwidget\n".to_owned())],
);
let workload: Vec<(String, String)> = files
.iter()
.map(|(path, body)| (format!("workload/{path}"), body.clone()))
.collect();
import.commit(
PUBLICATION_BRANCH,
Some(main),
"docs: add generic workload fixtures",
&workload,
);
import.run(&repo)?;
git(&repo, &["checkout", "-q", "main"])?;
git(
&repo,
&["remote", "add", "origin", &origin.to_string_lossy()],
)?;
git(&repo, &["push", "-q", "origin", "main"])?;
git(&repo, &["fetch", "-q", "origin"])?;
git(&repo, &["remote", "set-head", "origin", "main"])?;
git(
&repo,
&["branch", "-q", "--set-upstream-to=origin/main", "main"],
)?;
Ok((repo, origin))
}
#[derive(Default)]
struct Import {
bytes: Vec<u8>,
next: usize,
}
impl Import {
fn mark(&mut self) -> usize {
self.next += 1;
self.next
}
fn commit(
&mut self,
branch: &str,
parent: Option<usize>,
subject: &str,
files: &[(String, String)],
) -> usize {
let mut blobs = Vec::new();
for (path, body) in files {
let mark = self.mark();
write!(self.bytes, "blob\nmark :{mark}\ndata {}\n", body.len()).expect("memory");
self.bytes.extend_from_slice(body.as_bytes());
self.bytes.push(b'\n');
blobs.push((path, mark));
}
let mark = self.mark();
write!(
self.bytes,
"commit refs/heads/{branch}\nmark :{mark}\ncommitter Fixture \
<fixture@example.invalid> 1700000000 +0000\ndata {}\n{subject}\n",
subject.len()
)
.expect("memory");
if let Some(parent) = parent {
writeln!(self.bytes, "from :{parent}").expect("memory");
}
for (path, blob) in blobs {
writeln!(self.bytes, "M 100644 :{blob} {path}").expect("memory");
}
self.bytes.push(b'\n');
mark
}
fn run(self, repo: &Path) -> Result<()> {
git_input(repo, &["fast-import", "--quiet"], &self.bytes).map(|_| ())
}
}
fn invalid(reason: impl Into<String>) -> Error {
Error::Invalid {
reason: reason.into(),
}
}
fn io(error: std::io::Error) -> Error {
invalid(error.to_string())
}
fn git(repo: &Path, args: &[&str]) -> Result<String> {
git_input(repo, args, &[])
}
fn git_input(repo: &Path, args: &[&str], input: &[u8]) -> Result<String> {
let home = repo
.ancestors()
.find(|ancestor| ancestor.join(".gitconfig").is_file())
.unwrap_or(repo);
let mut child = Command::new("git")
.current_dir(repo)
.args(args)
.env_clear()
.env("PATH", std::env::var_os("PATH").unwrap_or_default())
.env("HOME", home)
.env("GIT_CONFIG_NOSYSTEM", "1")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.map_err(io)?;
child
.stdin
.take()
.expect("piped stdin")
.write_all(input)
.map_err(io)?;
let output = child.wait_with_output().map_err(io)?;
if !output.status.success() {
return Err(invalid(format!(
"workload git {} in {}: {}",
args.join(" "),
repo.display(),
String::from_utf8_lossy(&output.stderr)
)));
}
String::from_utf8(output.stdout).map_err(|error| invalid(error.to_string()))
}