Something went wrong. Try again.
Monorepo for Tangled forked from tangled.org/core
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184use std::{ io::Write, path::{Path, PathBuf}, time::SystemTime,};
use gix_error::{Exn, Message, ResultExt, message};use gix_hash::ObjectId;use gix_object::Find;
use super::{Options, Outcome, Split, write};use crate::Graph;
/// Write the commit-graph of the objects whose `info` directory is `info_dir`.////// `graph` must be the graph in `info_dir`, if any. It's closed before files are replaced./// See [`write()`] for `objects`, `tips` and `options`.////// * With [`Split::No`], `info_dir/commit-graph` is written, and the chain in `info_dir/commit-graphs` removed./// * Otherwise, a layer is written to `info_dir/commit-graphs` and `commit-graph-chain` is updated to end with it./// If `info_dir/commit-graph` stays below it, it becomes the bottom layer of the chain, otherwise it's removed.////// Afterwards, `.graph` files in `info_dir/commit-graphs` that aren't in the chain and weren't modified after/// `expire_time` are deleted. Merged layers count as modified now and `expire_time` defaults to now,/// so by default they are deleted right away.////// Returns `None` and changes nothing if there was nothing to write.pub fn write_to_info_dir( objects: &(impl Find + Clone + Send), info_dir: &Path, graph: Option<Graph>, tips: impl IntoIterator<Item = ObjectId>, options: Options, expire_time: Option<SystemTime>,) -> Result<Option<Outcome>, Exn<Message>> { let graphs_dir = info_dir.join("commit-graphs"); let layers: Vec<(PathBuf, ObjectId)> = graph .iter() .flat_map(|graph| graph.files.iter()) .map(|file| (file.path().to_owned(), file.checksum().to_owned())) .collect(); let lock = |path: &Path| { gix_lock::File::acquire_to_update_resource(path, gix_lock::acquire::Fail::Immediately, None) .or_raise(|| message!("Could not lock '{}'", path.display())) };
let (outcome, chain) = if options.split == Split::No { let path = info_dir.join("commit-graph"); let mut out = std::io::BufWriter::new(lock(&path)?); let Some(outcome) = write(objects, graph.as_ref(), tips, options, &mut out)? else { return Ok(None); }; drop(graph); commit(into_inner(out)?)?; make_read_only(&path); remove(&graphs_dir.join("commit-graph-chain"))?; (outcome, Vec::new()) } else { std::fs::create_dir_all(&graphs_dir) .or_raise(|| message!("Could not create directory '{}'", graphs_dir.display()))?; // Hold the chain's lock while writing the layer. let chain_path = graphs_dir.join("commit-graph-chain"); let mut chain_lock = lock(&chain_path)?; let layer = gix_tempfile::new( &graphs_dir, gix_tempfile::ContainingDirectory::Exists, gix_tempfile::AutoRemove::Tempfile, ) .or_raise(|| { message!( "Could not create a temporary file in '{}'", graphs_dir.display() ) })?; let mut out = std::io::BufWriter::new(layer); let Some(outcome) = write(objects, graph.as_ref(), tips, options, &mut out)? else { return Ok(None); }; drop(graph); let layer = into_inner(out)?;
let layer_path = |checksum: &ObjectId| graphs_dir.join(format!("graph-{checksum}.graph")); let base_layers = &layers[..outcome.num_base_layers]; match base_layers.last() { // A monolithic `commit-graph` kept below the new layer moves into the chain. Some((path, checksum)) if *path != layer_path(checksum) => { std::fs::rename(path, layer_path(checksum)).or_raise(|| { message!("Could not rename '{}' into the chain", path.display()) })?; } Some(_) => {} None => remove(&info_dir.join("commit-graph"))?, } let path = layer_path(&outcome.checksum); layer .persist(&path) .map_err(|err| err.error) .or_raise(|| message!("Could not write commit-graph layer '{}'", path.display()))?; make_read_only(&path);
let chain: Vec<ObjectId> = base_layers .iter() .map(|(_, checksum)| *checksum) .chain(Some(outcome.checksum)) .collect(); for checksum in &chain { writeln!(chain_lock, "{checksum}") .or_raise(|| message!("Could not write commit-graph chain"))?; } commit(chain_lock)?; make_read_only(&chain_path);
let now = SystemTime::now(); for (path, _) in &layers[outcome.num_base_layers..] { if let Ok(file) = std::fs::File::open(path) { file.set_modified(now).ok(); } } (outcome, chain.iter().map(layer_path).collect()) }; expire( &graphs_dir, &chain, expire_time.unwrap_or_else(SystemTime::now), )?; Ok(Some(outcome))}
fn into_inner<T: Write>(out: std::io::BufWriter<T>) -> Result<T, Exn<Message>> { out.into_inner() .map_err(std::io::IntoInnerError::into_error) .or_raise(|| message!("Could not write commit-graph"))}
fn commit(lock: gix_lock::File) -> Result<(), Exn<Message>> { let path = lock.resource_path(); lock.commit() .map_err(|err| err.error) .or_raise(|| message!("Could not write '{}'", path.display()))?; Ok(())}
/// Delete the `.graph` files in `graphs_dir` that aren't in `chain` and weren't modified after `expire_time`.fn expire( graphs_dir: &Path, chain: &[PathBuf], expire_time: SystemTime,) -> Result<(), Exn<Message>> { let Ok(entries) = std::fs::read_dir(graphs_dir) else { return Ok(()); }; for entry in entries.flatten() { let path = entry.path(); let expired = entry .metadata() .and_then(|meta| meta.modified()) .is_ok_and(|modified| modified <= expire_time); if expired && path.extension().is_some_and(|ext| ext == "graph") && !chain.contains(&path) { std::fs::remove_file(&path).ok(); } } Ok(())}
fn remove(path: &Path) -> Result<(), Exn<Message>> { match std::fs::remove_file(path) { Err(err) if err.kind() != std::io::ErrorKind::NotFound => { Err(err).or_raise(|| message!("Could not remove '{}'", path.display())) } _ => Ok(()), }}
fn make_read_only(path: &Path) { #[cfg(unix)] if let Ok(meta) = std::fs::metadata(path) { let mut permissions = meta.permissions(); permissions.set_readonly(true); std::fs::set_permissions(path, permissions).ok(); } #[cfg(not(unix))] let _ = path;}