use std::fs::File;
use std::io::{self, Read, Write};
use std::path::Path;
use std::sync::atomic::AtomicBool;
use super::{WriteLimits, WriteOptions, Written};
use crate::retention::MaintenanceIsolation;
use crate::{ObjectId, Repository};
#[derive(Clone, Copy, Debug)]
#[non_exhaustive]
pub struct Published {
pub anchor: ObjectId,
pub objects: usize,
pub commits: usize,
pub packs: usize,
}
#[derive(Debug, thiserror::Error)]
#[error("cannot {operation} bitmap index")]
#[non_exhaustive]
pub struct InstallError {
pub published: bool,
pub operation: &'static str,
#[source]
pub source: crate::Error,
}
impl Repository {
pub fn write_pack_bitmap<I: MaintenanceIsolation>(
&self,
isolation: &mut I,
pack: ObjectId,
options: WriteOptions,
) -> Result<Published, InstallError> {
self.write_pack_bitmap_with(
isolation,
pack,
options,
WriteLimits::default(),
&AtomicBool::new(false),
)
}
pub fn write_pack_bitmap_with<I: MaintenanceIsolation>(
&self,
isolation: &mut I,
pack: ObjectId,
options: WriteOptions,
limits: WriteLimits,
cancel: &AtomicBool,
) -> Result<Published, InstallError> {
install(self, isolation, Target::Pack(pack), options, limits, cancel)
}
pub fn write_multi_pack_bitmap<I: MaintenanceIsolation>(
&self,
isolation: &mut I,
midx: crate::multi_pack_index::WriteOptions,
options: WriteOptions,
) -> Result<Published, InstallError> {
self.write_multi_pack_bitmap_with(
isolation,
midx,
options,
WriteLimits::default(),
&AtomicBool::new(false),
)
}
pub fn write_multi_pack_bitmap_with<I: MaintenanceIsolation>(
&self,
isolation: &mut I,
midx: crate::multi_pack_index::WriteOptions,
options: WriteOptions,
limits: WriteLimits,
cancel: &AtomicBool,
) -> Result<Published, InstallError> {
install(
self,
isolation,
Target::Multi(midx),
options,
limits,
cancel,
)
}
}
enum Target {
Pack(ObjectId),
Multi(crate::multi_pack_index::WriteOptions),
}
fn install<I: MaintenanceIsolation>(
repository: &Repository,
isolation: &mut I,
target: Target,
options: WriteOptions,
limits: WriteLimits,
cancel: &AtomicBool,
) -> Result<Published, InstallError> {
super::write::check(cancel).map_err(|source| InstallError {
published: false,
operation: "check cancellation",
source: source.into(),
})?;
let _guard = isolation
.acquire(repository)
.map_err(|source| InstallError {
published: false,
operation: "acquire maintenance exclusion",
source: source.into(),
})?;
install_held(repository, target, options, limits, cancel)
}
pub(crate) fn write_repack_bitmap(
repository: &Repository,
pack: ObjectId,
limits: WriteLimits,
cancel: &AtomicBool,
) -> Result<Published, InstallError> {
install_held(
repository,
Target::Pack(pack),
WriteOptions::default(),
limits,
cancel,
)
}
fn install_held(
repository: &Repository,
target: Target,
options: WriteOptions,
limits: WriteLimits,
cancel: &AtomicBool,
) -> Result<Published, InstallError> {
let mut operation = "acquire writer lock";
let mut published = false;
let result = (|| -> crate::Result<Published> {
super::write::check(cancel)?;
let _operation = crate::locks::operation();
let directory = repository.object_dir().join("pack");
let lock_path = directory.join(match target {
Target::Pack(id) => format!("pack-{id}.bitmap.lock"),
Target::Multi(_) => "multi-pack-index.lock".to_owned(),
});
operation = "acquire writer lock";
let lock = crate::multi_pack_index::WriterLock::acquire(lock_path)?;
operation = "open source inventory";
let objects =
repository.objects_controlled(Default::default(), Default::default(), cancel)?;
operation = "encode graph artifacts";
let (index, written, packs, basename) = match target {
Target::Pack(id) => {
let written = objects.encode_pack_bitmap(id, options, limits, cancel)?;
(None, written, 1, directory.join(format!("pack-{id}")))
}
Target::Multi(midx) => {
let written = objects.encode_multi_pack_bitmap(midx, options, limits, cancel)?;
let packs = objects.primary_packs_with_paths().len();
let basename =
directory.join(format!("multi-pack-index-{}", written.bitmap.anchor));
(Some(written.index), written.bitmap, packs, basename)
}
};
let report = report(&written, packs);
operation = "synchronize private artifacts";
let reverse = private(&directory, &written.reverse_index)?;
let bitmap = private(&directory, &written.bitmap)?;
let index = index
.as_deref()
.map(|bytes| private(&directory, bytes))
.transpose()?;
operation = "revalidate source inventory";
for (_, pack) in objects.primary_packs_with_paths() {
super::write::check(cancel)?;
pack.check_unchanged()?;
}
lock.check()?;
super::write::check(cancel)?;
operation = "publish reverse index";
publish(reverse, &basename.with_extension("rev"), cancel)?;
operation = "publish bitmap";
publish(bitmap, &basename.with_extension("bitmap"), cancel)?;
if let Some(index) = index {
operation = "publish multi-pack index";
publish(index, &directory.join("multi-pack-index"), cancel)?;
}
published = true;
#[cfg(unix)]
{
operation = "synchronize pack directory";
File::open(&directory)?.sync_all()?;
}
operation = "release writer lock";
lock.release()?;
Ok(report)
})();
result.map_err(|source| InstallError {
published,
operation,
source,
})
}
fn report(written: &Written, packs: usize) -> Published {
Published {
anchor: written.anchor,
objects: written.objects,
commits: written.commits,
packs,
}
}
fn private(directory: &Path, bytes: &[u8]) -> io::Result<tempfile::NamedTempFile> {
let mut file = tempfile::NamedTempFile::new_in(directory)?;
file.write_all(bytes)?;
file.as_file().sync_all()?;
Ok(file)
}
fn publish(file: tempfile::NamedTempFile, path: &Path, cancel: &AtomicBool) -> crate::Result<()> {
super::write::check(cancel)?;
if let Ok(existing) = File::open(path) {
let length = file.as_file().metadata()?.len();
if existing.metadata()?.len() == length {
let mut old = Vec::new();
existing
.take(length.saturating_add(1))
.read_to_end(&mut old)?;
let mut new = Vec::new();
file.reopen()?.read_to_end(&mut new)?;
if old == new {
return Ok(());
}
}
}
file.persist(path).map_err(|error| error.error)?;
Ok(())
}