1use std::io::Write;
3use std::path::Path;
4use std::process::{Command, Stdio};
5
6use girt::{ObjectId, Repository};
7
8pub struct Gate {
9 pub started: tokio::sync::oneshot::Sender<()>,
10 pub release: std::sync::mpsc::Receiver<()>,
11}
12
13pub struct Fixture {
14 pub root: tempfile::TempDir,
15 tip: ObjectId,
16 blob: ObjectId,
17}
18
19impl Fixture {
20 pub fn new(packed: bool) -> Self {
21 let root = tempfile::tempdir().unwrap();
22 git(
23 root.path(),
24 &["init", "--bare", "--object-format=sha1", "--template=", "."],
25 b"",
26 );
27
28 let mut state = 42u32;
29 let bytes: Vec<u8> = (0..2 * 1024 * 1024)
30 .map(|_| {
31 state ^= state << 13;
32 state ^= state >> 17;
33 state ^= state << 5;
34 state as u8
35 })
36 .collect();
37
38 let blob = ObjectId::for_blob(girt::ObjectFormat::Sha1, &bytes);
39 let mut input = format!("blob\nmark :1\ndata {}\n", bytes.len()).into_bytes();
40 input.extend(bytes);
41 input.extend(b"\n");
42
43 for i in 0..512 {
44 input.extend(format!("commit refs/heads/main\ncommitter A <a@example.com> 1700000000 +0000\ndata {}\nnode {i}\n", format!("node {i}").len()).bytes());
45 if i == 0 {
46 input.extend(b"M 100644 :1 payload\n");
47 }
48 input.extend(b"\n");
49 }
50
51 git(root.path(), &["fast-import", "--quiet"], &input);
52
53 let pack_dir = root.path().join("objects/pack");
56 let pack = std::fs::read_dir(&pack_dir)
57 .unwrap()
58 .find_map(|e| {
59 let path = e.unwrap().path();
60 (path.extension().is_some_and(|e| e == "pack")).then_some(path)
61 })
62 .unwrap();
63
64 let data = std::fs::read(pack).unwrap();
65 std::fs::remove_dir_all(&pack_dir).unwrap();
66 std::fs::create_dir(&pack_dir).unwrap();
67 git(root.path(), &["unpack-objects"], &data);
68
69 if packed {
70 git(root.path(), &["repack", "-ad"], b"");
71 git(root.path(), &["prune-packed"], b"");
72 }
73
74 let tip = String::from_utf8(git(root.path(), &["rev-parse", "refs/heads/main"], b""))
75 .unwrap()
76 .trim()
77 .parse()
78 .unwrap();
79
80 Self { root, tip, blob }
81 }
82
83 pub fn operation(&self, gate: Option<Gate>) {
84 let repo = Repository::open(self.root.path()).unwrap();
85 let objects = repo.objects().unwrap();
86
87 if let Some(gate) = gate {
88 gate.started.send(()).unwrap();
89 gate.release.recv().unwrap();
90 }
91
92 let blob = objects.read(self.blob).unwrap().unwrap();
93 assert_eq!(blob.data().len(), 2 * 1024 * 1024);
94 assert_eq!(objects.walk(&[self.tip]).unwrap().len(), 512);
95 }
96}
97
98fn git(path: &Path, args: &[&str], input: &[u8]) -> Vec<u8> {
99 let mut command = Command::new("git");
100 for (key, _) in std::env::vars_os() {
101 if key.to_string_lossy().starts_with("GIT_") {
102 command.env_remove(key);
103 }
104 }
105
106 let mut child = command
107 .current_dir(path)
108 .args(args)
109 .env("GIT_CONFIG_NOSYSTEM", "1")
110 .env("GIT_CONFIG_GLOBAL", path.join("absent-config"))
111 .stdin(Stdio::piped())
112 .stdout(Stdio::piped())
113 .stderr(Stdio::piped())
114 .spawn()
115 .unwrap();
116
117 child.stdin.take().unwrap().write_all(input).unwrap();
118 let output = child.wait_with_output().unwrap();
119 assert!(
120 output.status.success(),
121 "{}",
122 String::from_utf8_lossy(&output.stderr)
123 );
124 output.stdout
125}