use std::io::Write;
use kernel::{Payload, TimeMs};
use crate::error::StorageError;
use super::commit::{Checkpoint, checkpoint_ref, committed, git_err, subject_of};
use super::provenance::Provenance;
use super::scan::CommitPlan;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BaseProgress {
Staged { files: usize },
Packed { bytes: usize },
}
impl Checkpoint {
pub fn base_checkpoint(
mut self,
scopes: &[String],
t: TimeMs,
of: &Provenance,
progress: &mut dyn FnMut(BaseProgress),
) -> Result<Payload, StorageError> {
let objects = self.repo.path().join("objects");
let objects = objects.to_str().ok_or_else(|| StorageError::Checkpoint {
op: "open the object store",
detail: format!("{} is not valid UTF-8", objects.display()),
})?;
let odb = git2::Odb::new().map_err(git_err("open an in-memory object store"))?;
let held = odb
.add_new_mempack_backend(1)
.map_err(git_err("open an in-memory object store"))?;
odb.add_disk_alternate(objects)
.map_err(git_err("read the object store"))?;
self.repo
.set_odb(&odb)
.map_err(git_err("hold the base checkpoint in memory"))?;
let files = self.stage_scopes_held(scopes)?;
progress(BaseProgress::Staged { files: files.len() });
self.scan_staged()?;
let onto_head = self.repo.head().is_err();
let oid = self.commit(&CommitPlan {
t,
of,
subject: &subject_of(scopes),
onto_head: false,
})?;
let mut pack = git2::Buf::new();
held.dump(&self.repo, &mut pack)
.map_err(git_err("dump the base checkpoint"))?;
let disk =
git2::Repository::open(self.repo.path()).map_err(git_err("open the object store"))?;
let disk_odb = disk.odb().map_err(git_err("open the object store"))?;
let mut writer = disk_odb
.packwriter()
.map_err(git_err("write the base checkpoint pack"))?;
writer
.write_all(&pack)
.map_err(|err| StorageError::Checkpoint {
op: "write the base checkpoint pack",
detail: err.to_string(),
})?;
writer
.commit()
.map_err(git_err("write the base checkpoint pack"))?;
progress(BaseProgress::Packed { bytes: pack.len() });
super::scan::write_index(&mut self.repo.index().map_err(git_err("read index"))?)?;
let name = if onto_head {
self.repo
.find_reference("HEAD")
.map_err(git_err("read HEAD"))?
.symbolic_target()
.map_err(|err| StorageError::Checkpoint {
op: "read HEAD",
detail: err.to_string(),
})?
.map(str::to_owned)
.ok_or_else(|| StorageError::Checkpoint {
op: "move HEAD to the base checkpoint",
detail: "HEAD names no branch".to_owned(),
})?
} else {
checkpoint_ref(of, oid)
};
self.repo
.reference(&name, oid, true, "sprawling: a base checkpoint")
.map_err(git_err("file a base checkpoint"))?;
held.reset()
.map_err(git_err("release the base checkpoint"))?;
committed(oid, of, scopes, files)
}
}
#[cfg(test)]
#[allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::indexing_slicing,
reason = "test code"
)]
mod tests;