Something went wrong. Try again.
Monorepo for Tangled tangled.org
Something went wrong. Try again.
5.6 kB · 147 lines
Rust
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148use crate::{BloomFilterSettings, File, Graph, MAX_COMMITS};use gix_error::{ErrorExt, Exn, Message, ResultExt, message};use std::{ io::{BufRead, BufReader}, path::Path,};
/// Instantiate a `Graph` from various sources.impl Graph { /// Instantiate a commit graph from `path` which may be a directory containing graph files or the graph file itself. pub fn at(path: &Path) -> Result<Self, Exn<Message>> { Self::try_from(path) }
/// Instantiate a commit graph from the directory containing all of its files. /// /// Loading stops at the first layer that can't be opened or parsed, whose checksum doesn't match its /// name in the chain, or whose `BASE` chunk doesn't list the layers below it in chain order, keeping only the /// layers loaded so far. It's an error if no layer could be loaded. pub fn from_commit_graphs_dir(path: &Path) -> Result<Self, Exn<Message>> { let commit_graphs_dir = path; let chain_file_path = commit_graphs_dir.join("commit-graph-chain"); let chain_file = std::fs::File::open(&chain_file_path).or_raise(|| { message!( "Could not open commit-graph chain file at '{}'", chain_file_path.display() ) })?; let mut files = Vec::new(); for line in BufReader::new(chain_file).lines() { let hash = line.or_raise(|| { message!( "Could not read from commit-graph file at '{}'", chain_file_path.display() ) })?; let Ok(id) = gix_hash::ObjectId::from_hex(hash.as_bytes()) else { break; }; let Ok(file) = File::at(commit_graphs_dir.join(format!("graph-{hash}.graph"))) else { break; }; if file.checksum() != id || !base_graphs_match(&file, &files) { break; }
files.push(file); } Ok(Self::new(files)?) }
/// Instantiate a commit graph from a `.git/objects/info/commit-graph` or /// `.git/objects/info/commit-graphs/graph-*.graph` file. pub fn from_file(path: &Path) -> Result<Self, Exn<Message>> { let file = File::at(path) .or_raise(|| message!("Could not open commit-graph file at '{}'", path.display()))?; Ok(Self::new(vec![file])?) }
/// Instantiate a commit graph from an `.git/objects/info` directory. pub fn from_info_dir(info_dir: &Path) -> Result<Self, Exn<Message>> { Self::from_file(&info_dir.join("commit-graph")) .or_else(|_| Self::from_commit_graphs_dir(&info_dir.join("commit-graphs"))) }
/// Create a new commit graph from a list of `files`. pub fn new(files: Vec<File>) -> Result<Self, Message> { let mut files = nonempty::NonEmpty::from_vec(files) .ok_or_else(|| message!("Commit-graph must contain at least one file"))?; let num_commits: u64 = files.iter().map(|f| u64::from(f.num_commits())).sum(); if num_commits > u64::from(MAX_COMMITS) { return Err(message!( "Commit-graph files contain {num_commits} commits altogether, but only {MAX_COMMITS} commits are allowed" )); }
let mut f1 = files.first(); for f2 in files.tail() { if f1.object_hash() != f2.object_hash() { return Err(message!( "Commit-graph files mismatch: '{path1}' uses hash {hash1:?}, but '{path2}' uses hash {hash2:?}", path1 = f1.path().display(), hash1 = f1.object_hash(), path2 = f2.path().display(), hash2 = f2.object_hash(), )); } f1 = f2; }
disable_incompatible_bloom_layers(&mut files); if !files.iter().all(File::has_generation_data) { files.iter_mut().for_each(File::disable_generation_data); }
Ok(Self { files }) }}
/// Return `true` if `file`'s `BASE` chunk lists the layers `below` it in chain order.fn base_graphs_match(file: &File, below: &[File]) -> bool { usize::from(file.base_graph_count()) >= below.len() && file .iter_base_graph_ids() .zip(below) .all(|(base, below)| base == below.checksum())}
fn disable_incompatible_bloom_layers(files: &mut nonempty::NonEmpty<File>) { let mut preferred: Option<BloomFilterSettings> = None; for file in files.iter_mut().rev() { let Some(settings) = file.bloom_filter_settings else { continue; }; if preferred.is_none() { preferred = Some(settings); continue; } if preferred != Some(settings) { file.clear_bloom_filters(); } }}
impl TryFrom<&Path> for Graph { type Error = Exn<Message>;
fn try_from(path: &Path) -> Result<Self, Self::Error> { if path.is_file() { // Assume we are looking at `.git/objects/info/commit-graph` or // `.git/objects/info/commit-graphs/graph-*.graph`. Self::from_file(path) } else if path.is_dir() { if path.join("commit-graph-chain").is_file() { Self::from_commit_graphs_dir(path) } else { Self::from_info_dir(path) } } else { Err(message!( "Did not find any files that look like commit graphs at '{}'", path.display() ) .raise()) } }}