use std::{ collections::{HashMap, HashSet}, convert::{TryFrom, TryInto}, hash::BuildHasher, io::{BufRead, Cursor}, path::Path, process::Command, }; use gix_commitgraph::{Graph, Position as GraphPosition}; use gix_testtools::scripted_fixture_read_only; mod access; pub fn check_common(cg: &Graph, expected: &HashMap) { cg.verify_integrity(|_| Ok::<_, std::convert::Infallible>(())) .expect("graph is valid"); assert_eq!( usize::try_from(cg.num_commits()).expect("an architecture able to hold 32 bits of integer"), expected.len() ); for ref_info in expected.values() { assert_eq!(cg.id_at(ref_info.pos()), ref_info.id(), "id_at({})", ref_info.pos()); assert_eq!( cg.lookup(ref_info.id()), Some(ref_info.pos()), "lookup({})", ref_info.id() ); let expected_parents: Vec<_> = ref_info .parent_ids() .map(|id| { expected .values() .find(|item| item.id() == id) .expect("find RefInfo by id") }) .collect(); let commit = cg.commit_at(ref_info.pos()); assert_eq!(commit.id(), ref_info.id()); assert_eq!( commit.committer_timestamp(), ref_info.time.seconds.try_into().expect("timestamp in bounds") ); assert_eq!(commit.root_tree_id(), ref_info.root_tree_id()); assert_eq!( commit.parent1().expect("failed to access commit's parent1"), expected_parents.iter().map(|x| x.pos()).next() ); assert_eq!( commit .iter_parents() .collect::, _>>() .expect("failed to access commit's parents"), expected_parents.iter().map(|x| x.pos()).collect::>() ); } assert_eq!( cg.iter_ids().collect::>(), expected.values().map(RefInfo::id).collect::>() ); } pub fn graph_and_expected( script_path: &str, refs: &[&'static str], ) -> (gix_commitgraph::Graph, HashMap) { graph_and_expected_named(script_path, "", refs) } pub fn graph_and_expected_named( script_path: &str, name: &str, refs: &[&'static str], ) -> (gix_commitgraph::Graph, HashMap) { let repo_dir = scripted_fixture_read_only(script_path) .expect("script succeeds all the time") .join(name); let expected = inspect_refs(&repo_dir, refs); let cg = Graph::from_info_dir(repo_dir.join(".git").join("objects").join("info")).expect("graph present and valid"); (cg, expected) } pub struct RefInfo { id: gix_hash::ObjectId, pub time: gix_date::Time, parent_ids: Vec, pos: GraphPosition, root_tree_id: gix_hash::ObjectId, } impl RefInfo { pub fn id(&self) -> &gix_hash::oid { &self.id } pub fn pos(&self) -> GraphPosition { self.pos } pub fn parent_ids(&self) -> impl Iterator { self.parent_ids.iter().map(AsRef::as_ref) } pub fn root_tree_id(&self) -> &gix_hash::oid { &self.root_tree_id } } fn inspect_refs(repo_dir: impl AsRef, refs: &[&'static str]) -> HashMap { let output = Command::new("git") .arg("-C") .arg(repo_dir.as_ref()) .arg("show") .arg("--no-patch") .arg("--pretty=format:%S %H %T %ct %P") .args(refs) .arg("--") .env_remove("GIT_DIR") .output() .expect("failed to execute `git show`"); // Output format: let mut infos: Vec<_> = Cursor::new(output.stdout) .lines() .map(|x| x.expect("failed to read `git show` output")) .map(|x| { let parts = x.trim_end().split(' ').collect::>(); ( parts[0].to_string(), gix_hash::ObjectId::from_hex(parts[1].as_bytes()).expect("40 bytes hex"), gix_hash::ObjectId::from_hex(parts[2].as_bytes()).expect("40 bytes hex"), gix_date::Time::new(parts[3].parse().expect("valid stamp"), 0), parts[4..] .iter() .map(|x| gix_hash::ObjectId::from_hex(x.as_bytes()).expect("40 bytes hex")) .collect(), ) }) .collect(); infos.sort_by_key(|x| x.1); let get_pos = |id: &gix_hash::oid| -> GraphPosition { let pos: u32 = infos .binary_search_by_key(&id, |x| &x.1) .expect("sorted_ids to contain id") .try_into() .expect("graph position to fit in u32"); GraphPosition(pos) }; infos .iter() .cloned() .map(|(name, id, root_tree_id, time, parent_ids)| { ( name, RefInfo { id, parent_ids, root_tree_id, time, pos: get_pos(&id), }, ) }) .collect() }