This repository has no description
0

Configure Feed

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

core / knot2 / crates / knot-git / tests / bitmap.rs
6.6 kB 217 lines
1use std::collections::HashSet; 2use std::io::Read; 3use std::path::{Path, PathBuf}; 4 5use knot_git::{ 6 Haves, Repo, Wants, reachable_via_bitmap, verbatim_clone_pack, write_bitmap, write_midx_bitmap, 7}; 8use knot_types::Oid; 9 10mod common; 11use common::{commit_file as commit, git, git_available, git_ok as ok}; 12 13fn test_bitmap(path: &Path) { 14 let (ok, report) = git(path, &["rev-list", "--test-bitmap", "HEAD"]); 15 assert!(ok, "git rejected the bitmap: {report}"); 16} 17 18fn seed(format: &str) -> tempfile::TempDir { 19 let dir = tempfile::tempdir().unwrap(); 20 let path = dir.path(); 21 ok( 22 path, 23 &[ 24 "init", 25 "-q", 26 "--object-format", 27 format, 28 "--initial-branch", 29 "main", 30 ], 31 ); 32 commit(path, "a.txt", "alpha\n", "root"); 33 commit(path, "b.txt", "beta\n", "second"); 34 ok(path, &["checkout", "-q", "-b", "feature"]); 35 commit(path, "c.txt", "gamma\n", "feature work"); 36 ok(path, &["checkout", "-q", "main"]); 37 commit(path, "d.txt", "delta\n", "more main"); 38 ok(path, &["tag", "-a", "v1", "-m", "release one"]); 39 ok(path, &["repack", "-adq"]); 40 dir 41} 42 43fn find_pack(path: &Path, suffix: &str) -> PathBuf { 44 std::fs::read_dir(path.join(".git/objects/pack")) 45 .unwrap() 46 .filter_map(Result::ok) 47 .map(|e| e.path()) 48 .find(|p| { 49 p.file_name() 50 .and_then(|n| n.to_str()) 51 .is_some_and(|n| n.ends_with(suffix)) 52 }) 53 .unwrap_or_else(|| panic!("a pack file ending {suffix}")) 54} 55 56fn idx_count(path: &Path) -> usize { 57 std::fs::read_dir(path.join(".git/objects/pack")) 58 .unwrap() 59 .filter_map(Result::ok) 60 .filter(|e| e.path().extension().is_some_and(|ext| ext == "idx")) 61 .count() 62} 63 64fn rev_parse(path: &Path, name: &str) -> Oid { 65 Oid::from_hex(ok(path, &["rev-parse", name]).trim()).unwrap() 66} 67 68fn rev_list(path: &Path, revs: &[&str]) -> HashSet<Oid> { 69 let args: Vec<&str> = ["rev-list", "--objects"] 70 .into_iter() 71 .chain(revs.iter().copied()) 72 .collect(); 73 ok(path, &args) 74 .lines() 75 .filter_map(|line| line.split_whitespace().next()) 76 .filter_map(|token| Oid::from_hex(token).ok()) 77 .collect() 78} 79 80fn closure(repo: &Repo, wants: &[Oid], haves: &[Oid]) -> HashSet<Oid> { 81 reachable_via_bitmap(repo, Wants::new(wants), Haves::new(haves)) 82 .unwrap() 83 .expect("bitmap fast path resolves") 84 .into_iter() 85 .collect() 86} 87 88fn walked(repo: &Repo, want: Oid) -> HashSet<Oid> { 89 repo.select_pack_objects(Wants::new(&[want]), Haves::new(&[])) 90 .unwrap() 91 .into_iter() 92 .collect() 93} 94 95fn reject_corruption(repo: &Repo, bitmap: &Path, head: Oid) { 96 let intact = std::fs::read(bitmap).unwrap(); 97 let mut flipped = intact.clone(); 98 *flipped.last_mut().unwrap() ^= 0xff; 99 std::fs::write(bitmap, &flipped).unwrap(); 100 assert!( 101 reachable_via_bitmap(repo, Wants::new(&[head]), Haves::new(&[])).is_err(), 102 "a corrupt checksum is rejected" 103 ); 104 std::fs::write(bitmap, &intact[..intact.len() / 2]).unwrap(); 105 assert!( 106 reachable_via_bitmap(repo, Wants::new(&[head]), Haves::new(&[])).is_err(), 107 "a truncated bitmap is rejected without a panic" 108 ); 109} 110 111fn run_lifecycle(format: &str) { 112 if !git_available() { 113 eprintln!("skipping bitmap lifecycle: git unavailable"); 114 return; 115 } 116 let dir = seed(format); 117 let path = dir.path(); 118 let repo = Repo::open(path).unwrap(); 119 let idx = find_pack(path, ".idx"); 120 assert!( 121 write_bitmap(&repo, &idx).unwrap(), 122 "a single-pack repo gets a bitmap" 123 ); 124 test_bitmap(path); 125 126 let head = rev_parse(path, "HEAD"); 127 let ours = closure(&repo, &[head], &[]); 128 assert_eq!( 129 ours, 130 rev_list(path, &["HEAD"]), 131 "closure equals rev-list --objects HEAD" 132 ); 133 assert_eq!(ours, walked(&repo, head), "closure equals the walk closure"); 134 assert_eq!( 135 ours, 136 closure(&repo, &vec![head; 2048], &[]), 137 "duplicate wants fold to one closure" 138 ); 139 140 let feature = rev_parse(path, "feature"); 141 let main = rev_parse(path, "main"); 142 assert_eq!( 143 closure(&repo, &[feature], &[main]), 144 rev_list(path, &["feature", "^main"]), 145 "feature minus main" 146 ); 147 148 let tag = rev_parse(path, "v1"); 149 let mut whole = verbatim_clone_pack(&repo, Wants::new(&[main, feature, tag])) 150 .unwrap() 151 .expect("full clone reuses whole pack"); 152 let mut header = [0u8; 12]; 153 whole.read_exact(&mut header).unwrap(); 154 assert_eq!( 155 &header[..4], 156 b"PACK", 157 "verbatim reuse returns the on-disk pack" 158 ); 159 let count = u32::from_be_bytes([header[8], header[9], header[10], header[11]]) as usize; 160 assert_eq!( 161 count, 162 rev_list(path, &["--all"]).len(), 163 "verbatim reuse streams every object" 164 ); 165 assert!( 166 verbatim_clone_pack(&repo, Wants::new(&[main])) 167 .unwrap() 168 .is_none(), 169 "a partial closure never reuses the whole pack" 170 ); 171 172 ok(path, &["tag", "-a", "v2", "-m", "release two", "HEAD"]); 173 let v2 = rev_parse(path, "v2"); 174 assert_ne!(v2, head); 175 assert!( 176 reachable_via_bitmap(&repo, Wants::new(&[v2]), Haves::new(&[])) 177 .unwrap() 178 .is_none(), 179 "a want outside the bitmapped pack fails closed" 180 ); 181 assert!( 182 walked(&repo, v2).contains(&v2), 183 "the fallback walk includes the wanted tag" 184 ); 185 186 std::fs::remove_file(idx.with_extension("bitmap")).unwrap(); 187 commit(path, "e.txt", "epsilon\n", "post-bitmap growth"); 188 commit(path, "f.txt", "zeta\n", "more growth"); 189 ok(path, &["repack", "-dq"]); 190 ok(path, &["multi-pack-index", "write"]); 191 assert!(idx_count(path) >= 2, "the repo now spans multiple packs"); 192 193 let repo = Repo::open(path).unwrap(); 194 assert!( 195 write_midx_bitmap(&repo).unwrap(), 196 "a multi-pack repo gets a midx bitmap" 197 ); 198 test_bitmap(path); 199 let head = rev_parse(path, "HEAD"); 200 assert_eq!( 201 closure(&repo, &[head], &[]), 202 rev_list(path, &["HEAD"]), 203 "midx closure equals rev-list HEAD" 204 ); 205 206 reject_corruption(&repo, &find_pack(path, ".bitmap"), head); 207} 208 209#[test] 210fn bitmap_single_pack_then_midx_round_trips_against_canonical_git_sha1() { 211 run_lifecycle("sha1"); 212} 213 214#[test] 215fn bitmap_single_pack_then_midx_round_trips_against_canonical_git_sha256() { 216 run_lifecycle("sha256"); 217}