use std::io::Write as _;
use std::path::Path;
use std::process::Stdio;
use knot_cobs::Ticket;
use knot_record::ticket::TicketState;
use knot_runtime::{K256Signer, SeededEntropy};
use knot_types::{AccountDid, RepoDid};
use crate::common::cobs::Cobs;
use crate::common::git_command;
const EPOCH: u64 = 1_700_000_000;
const COMMITTER: &str = "Bench <bench@example.test>";
pub fn source(dir: &Path, commits: usize, refs: usize) {
std::fs::create_dir_all(dir).unwrap();
let init = git_command(dir)
.args(["init", "-q", "--bare", "-b", "main"])
.output()
.unwrap();
assert!(init.status.success(), "git init: {init:?}");
let mut stream = Vec::new();
for index in 0..commits {
commit(&mut stream, index, None, EPOCH + index as u64);
}
for index in 1..refs {
let mark = 1 + index * commits / refs;
if index % 2 == 1 {
writeln!(stream, "reset refs/heads/branch/{index}\nfrom :{mark}\n").unwrap();
} else {
let message = format!("release {index}");
writeln!(
stream,
"tag v{index}\nfrom :{mark}\ntagger {COMMITTER} {} +0000\ndata {}\n{message}",
EPOCH + mark as u64,
message.len()
)
.unwrap();
}
}
fast_import(dir, &stream);
}
/// dated a second after the tip, since git bounds its walks by commit date.
pub fn advance(dir: &Path, index: usize) {
let tip = git_command(dir)
.args(["log", "-1", "--format=%ct", "refs/heads/main"])
.output()
.unwrap();
let tip: u64 = String::from_utf8(tip.stdout).unwrap().trim().parse().unwrap();
let mut stream = Vec::new();
commit(&mut stream, index, Some("refs/heads/main^0"), tip + 1);
fast_import(dir, &stream);
}
fn commit(stream: &mut Vec<u8>, index: usize, from: Option<&str>, date: u64) {
let message = format!("change {index}");
let body = body(index);
writeln!(
stream,
"commit refs/heads/main\nmark :{}\ncommitter {COMMITTER} {} +0000\ndata {}\n{message}",
index + 1,
date,
message.len()
)
.unwrap();
if let Some(from) = from {
writeln!(stream, "from {from}").unwrap();
}
writeln!(
stream,
"M 100644 inline src/d{}/f{}.txt\ndata {}\n{body}",
index % 32,
index % 997,
body.len()
)
.unwrap();
}
/// the body differs per commit, or the packs delta against one repeated line.
fn body(index: usize) -> String {
let mut state = index as u64 ^ 0x9e37_79b9_7f4a_7c15;
(0..8)
.map(|_| {
state ^= state << 13;
state ^= state >> 7;
state ^= state << 17;
format!("{state:016x} {index}\n")
})
.collect::<Vec<_>>()
.concat()
.repeat(1 + index % 3)
}
fn fast_import(dir: &Path, stream: &[u8]) {
let mut child = git_command(dir)
.args(["fast-import", "--quiet"])
.stdin(Stdio::piped())
.spawn()
.unwrap();
child.stdin.take().unwrap().write_all(stream).unwrap();
let status = child.wait().unwrap();
assert!(status.success(), "git fast-import: {status}");
}
pub fn tickets(repo: &Path, did: &str, owner: &str, tickets: usize, versions: usize) {
let repo = knot_git::Repo::open(repo).unwrap();
let did = RepoDid::new(did).unwrap();
let author = AccountDid::new(owner).unwrap();
let signer = K256Signer::generate(&SeededEntropy::new(5));
let cobs = Cobs {
repo: &repo,
did: &did,
signer: &signer,
};
for number in 1..=tickets {
let body = Ticket {
state: TicketState::Open,
tangled_labels: None,
assignees: None,
};
let text = format!("ticket {number}");
let (object, mut version) =
cobs.open(&number.to_string(), &author, number as u32, body, &text, 10);
for edit in 1..versions {
version = cobs.edit_ticket(
object,
&author,
version,
&format!("{text} version {edit}"),
10 + edit as i64,
);
}
}
}