This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

7.0 kB 223 lines
1#![allow(dead_code, unused_imports)] 2 3use std::collections::BTreeSet; 4use std::ops::Range; 5use std::path::Path; 6 7use knot_git::{ 8 EntryKind, Identity, Layout, NewCommit, RefUpdate, Repo, StagedAction, StagedChange, 9}; 10use knot_maintenance::{GeometricFactor, ObjectCount, Options, PruneGrace, ReflogRetention}; 11use knot_types::{AuthorName, BranchName, Email, ObjectFormat, Oid, RefName, RepoDid, UnixSeconds}; 12 13pub const EMPTY_TREE_SHA1: &str = "4b825dc642cb6eb9a060e54bf8d69288fbee4904"; 14pub const EMPTY_TREE_SHA256: &str = 15 "6ef19b41225c5369f1c104d45d8d85efa9b057b53b14b4b9b939dd74decc5321"; 16 17pub fn empty_tree(format: ObjectFormat) -> &'static str { 18 if format == ObjectFormat::SHA256 { 19 EMPTY_TREE_SHA256 20 } else { 21 EMPTY_TREE_SHA1 22 } 23} 24 25pub fn now() -> UnixSeconds { 26 UnixSeconds::new(1_700_000_500) 27} 28 29pub use knot_fixtures::available as git_available; 30 31pub fn identity() -> Identity { 32 Identity { 33 name: AuthorName::new("nel"), 34 email: Email::new("nel@oyster.cafe"), 35 time: UnixSeconds::new(1_700_000_000), 36 offset_seconds: 0, 37 } 38} 39 40pub fn options() -> Options { 41 Options { 42 repack_max_objects: ObjectCount::new(1_000_000), 43 geometric_factor: GeometricFactor::full_repack(), 44 prune_grace: PruneGrace::from_secs(0), 45 reflog_floor: ReflogRetention::from_secs(i64::MAX as u64 / 4), 46 commit_graph: true, 47 multi_pack_index: true, 48 bitmap: true, 49 } 50} 51 52pub fn create_repo(scan: &Path, format: ObjectFormat, did: &str) -> Repo { 53 Layout::new(scan) 54 .with_object_format(format) 55 .with_default_branch(BranchName::new("main").unwrap()) 56 .create(&RepoDid::new(did).unwrap()) 57 .unwrap() 58} 59 60pub fn commit_on(repo: &Repo, empty_tree: &str, body: u8, parents: Vec<Oid>) -> Oid { 61 let tree = repo 62 .write_staged_tree( 63 Oid::from_hex(empty_tree).unwrap(), 64 &[StagedChange { 65 path: knot_types::RepoPath::new(format!("file{body}.txt")).unwrap(), 66 action: StagedAction::Put { 67 content: vec![body, body, body], 68 kind: EntryKind::Blob, 69 }, 70 }], 71 ) 72 .unwrap(); 73 repo.write_commit(&NewCommit { 74 tree, 75 parents, 76 author: identity(), 77 committer: identity(), 78 message: format!("commit {body}"), 79 extra_headers: Vec::new(), 80 }) 81 .unwrap() 82} 83 84pub fn commit(repo: &Repo, body: u8, parents: Vec<Oid>) -> Oid { 85 commit_on(repo, EMPTY_TREE_SHA1, body, parents) 86} 87 88pub fn chain(repo: &Repo, empty_tree: &str, bodies: Range<u8>, start: Option<Oid>) -> Oid { 89 bodies 90 .fold(start, |parent, body| { 91 let parents = parent.map(|tip| vec![tip]).unwrap_or_default(); 92 Some(commit_on(repo, empty_tree, body, parents)) 93 }) 94 .expect("a non-empty body range yields a tip") 95} 96 97pub fn set_ref(repo: &Repo, name: &str, new: Oid) { 98 let refname = RefName::new(name).unwrap(); 99 let update = match repo.find_ref(&refname).unwrap() { 100 Some(old) => RefUpdate::Update { 101 name: refname, 102 old, 103 new, 104 }, 105 None => RefUpdate::Create { name: refname, new }, 106 }; 107 repo.update_ref(&update).unwrap(); 108} 109 110pub fn delete_ref(repo: &Repo, name: &str) { 111 let refname = RefName::new(name).unwrap(); 112 let old = repo.find_ref(&refname).unwrap().unwrap(); 113 repo.update_ref(&RefUpdate::Delete { name: refname, old }) 114 .unwrap(); 115} 116 117pub fn set_reflog_seconds(repo: &Repo, name: &str, seconds: &[i64]) { 118 let path = repo.git().git_dir().join("logs").join(name); 119 let text = std::fs::read_to_string(&path).expect("reflog file exists"); 120 let lines: Vec<&str> = text.lines().collect(); 121 assert_eq!( 122 lines.len(), 123 seconds.len(), 124 "set_reflog_seconds needs one timestamp per reflog line" 125 ); 126 let rewritten = lines 127 .iter() 128 .zip(seconds) 129 .map(|(line, secs)| rewrite_reflog_seconds(line, *secs)) 130 .collect::<Vec<_>>() 131 .join("\n"); 132 std::fs::write(&path, format!("{rewritten}\n")).expect("rewrite reflog"); 133} 134 135fn rewrite_reflog_seconds(line: &str, secs: i64) -> String { 136 let (meta, message) = line 137 .split_once('\t') 138 .expect("reflog line has a message tab"); 139 let tokens: Vec<&str> = meta.split(' ').collect(); 140 let tz = tokens.last().expect("reflog line has a timezone"); 141 let head = tokens[..tokens.len() - 2].join(" "); 142 format!("{head} {secs} {tz}\t{message}") 143} 144 145pub fn git(repo: &Repo, args: &[&str]) -> (bool, String) { 146 let out = knot_fixtures::command(repo.git().git_dir()) 147 .args(args) 148 .output() 149 .expect("git runs"); 150 ( 151 out.status.success(), 152 String::from_utf8_lossy(&out.stderr).into_owned(), 153 ) 154} 155 156pub fn fsck(repo: &Repo) -> (bool, String) { 157 git(repo, &["fsck", "--no-dangling", "--no-progress"]) 158} 159 160pub fn fsck_clean(repo: &Repo) -> bool { 161 fsck(repo).0 162} 163 164pub fn assert_fsck_clean(repo: &Repo) { 165 let (clean, stderr) = fsck(repo); 166 assert!(clean, "fsck failed: {stderr}"); 167} 168 169pub fn reachable_objects(repo: &Repo) -> BTreeSet<String> { 170 let out = knot_fixtures::command(repo.git().git_dir()) 171 .args(["rev-list", "--objects", "--all"]) 172 .output() 173 .expect("git rev-list runs"); 174 assert!(out.status.success()); 175 String::from_utf8_lossy(&out.stdout) 176 .lines() 177 .filter_map(|line| line.split_whitespace().next()) 178 .map(str::to_string) 179 .collect() 180} 181 182pub fn midx_verifies(repo: &Repo) -> Option<(bool, String)> { 183 let midx = repo.git().git_dir().join("objects/pack/multi-pack-index"); 184 midx.exists() 185 .then(|| git(repo, &["multi-pack-index", "verify"])) 186} 187 188fn pack_entries(repo: &Repo) -> impl Iterator<Item = std::path::PathBuf> { 189 std::fs::read_dir(repo.git().git_dir().join("objects/pack")) 190 .into_iter() 191 .flatten() 192 .filter_map(Result::ok) 193 .map(|entry| entry.path()) 194} 195 196pub fn idx_stems(repo: &Repo) -> BTreeSet<String> { 197 pack_entries(repo) 198 .filter(|path| path.extension().is_some_and(|ext| ext == "idx")) 199 .filter_map(|path| { 200 path.file_stem() 201 .and_then(|stem| stem.to_str()) 202 .map(str::to_string) 203 }) 204 .collect() 205} 206 207pub fn has_cruft_pack(repo: &Repo) -> bool { 208 pack_entries(repo).any(|path| path.extension().is_some_and(|ext| ext == "mtimes")) 209} 210 211pub fn has_bitmap(repo: &Repo) -> bool { 212 pack_entries(repo).any(|path| path.extension().is_some_and(|ext| ext == "bitmap")) 213} 214 215pub fn has_midx_bitmap(repo: &Repo) -> bool { 216 pack_entries(repo) 217 .filter_map(|path| { 218 path.file_name() 219 .and_then(|n| n.to_str()) 220 .map(str::to_string) 221 }) 222 .any(|name| name.starts_with("multi-pack-index-") && name.ends_with(".bitmap")) 223}