storage/checkpoint/
base.rs1use std::io::Write;
9
10use kernel::{Payload, TimeMs};
11
12use crate::error::StorageError;
13
14use super::commit::{Checkpoint, checkpoint_ref, committed, git_err, subject_of};
15use super::opening::{HeadMove, IndexOwner, commit_refused};
16use super::provenance::Provenance;
17use super::scan::CommitPlan;
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
21pub enum BaseProgress {
22 Staged { files: usize },
24 Packed { bytes: usize },
26}
27
28pub(super) struct BaseOf<'a> {
31 pub(super) scopes: &'a [String],
32 pub(super) t: TimeMs,
33 pub(super) of: &'a Provenance,
34}
35
36pub(super) enum BaseTarget {
38 Head,
41 Filed,
43}
44
45impl Checkpoint {
46 pub fn base_checkpoint(
63 self,
64 scopes: &[String],
65 t: TimeMs,
66 of: &Provenance,
67 progress: &mut dyn FnMut(BaseProgress),
68 ) -> Result<Payload, StorageError> {
69 if Self::head_commit(&self.repo)?.is_some() {
70 return self.write_held(&BaseOf { scopes, t, of }, BaseTarget::Filed, progress);
71 }
72 let _head_moves = super::opening::moving_head()?;
73 let target = match Self::head_commit(&self.repo)? {
74 None => BaseTarget::Head,
75 Some(_) => BaseTarget::Filed,
76 };
77 self.write_held(&BaseOf { scopes, t, of }, target, progress)
78 }
79
80 pub(super) fn reopened(&self) -> Result<Checkpoint, StorageError> {
86 let repo = git2::Repository::open(self.repo.path())
87 .map_err(git_err("open the city repository for a base"))?;
88 let index = match &self.index {
89 IndexOwner::City => IndexOwner::City,
90 IndexOwner::Writer(own) => {
91 let mut index = git2::Index::open(own).map_err(git_err("open a writer's index"))?;
92 repo.set_index(&mut index)
93 .map_err(git_err("give a writer its index"))?;
94 IndexOwner::Writer(own.clone())
95 }
96 };
97 Ok(Checkpoint {
98 repo,
99 last: None,
100 index,
101 })
102 }
103
104 pub(super) fn write_held(
107 mut self,
108 base: &BaseOf<'_>,
109 target: BaseTarget,
110 progress: &mut dyn FnMut(BaseProgress),
111 ) -> Result<Payload, StorageError> {
112 let objects = self.repo.path().join("objects");
113 let objects = objects.to_str().ok_or_else(|| StorageError::Checkpoint {
114 op: "open the object store",
115 detail: format!("{} is not valid UTF-8", objects.display()),
116 })?;
117 let odb = git2::Odb::new().map_err(git_err("open an in-memory object store"))?;
120 let held = odb
121 .add_new_mempack_backend(1)
122 .map_err(git_err("open an in-memory object store"))?;
123 odb.add_disk_alternate(objects)
124 .map_err(git_err("read the object store"))?;
125 self.repo
126 .set_odb(&odb)
127 .map_err(git_err("hold the base checkpoint in memory"))?;
128
129 let BaseOf { scopes, t, of } = *base;
130 let files = self.stage_scopes_held(scopes)?;
131 progress(BaseProgress::Staged { files: files.len() });
132 self.scan_staged()?;
133 let oid = self.commit(&CommitPlan {
134 t,
135 of,
136 subject: &subject_of(scopes),
137 head: HeadMove::Leave,
138 })?;
139
140 let mut pack = git2::Buf::new();
141 held.dump(&self.repo, &mut pack)
142 .map_err(git_err("dump the base checkpoint"))?;
143 let disk =
144 git2::Repository::open(self.repo.path()).map_err(git_err("open the object store"))?;
145 let disk_odb = disk.odb().map_err(git_err("open the object store"))?;
146 let mut writer = disk_odb
147 .packwriter()
148 .map_err(git_err("write the base checkpoint pack"))?;
149 writer
150 .write_all(&pack)
151 .map_err(|err| StorageError::Checkpoint {
152 op: "write the base checkpoint pack",
153 detail: err.to_string(),
154 })?;
155 writer
156 .commit()
157 .map_err(git_err("write the base checkpoint pack"))?;
158 progress(BaseProgress::Packed { bytes: pack.len() });
159
160 super::scan::write_index(&mut self.repo.index().map_err(git_err("read index"))?)?;
163 match target {
164 BaseTarget::Head => {
167 let branch = self
168 .repo
169 .find_reference("HEAD")
170 .map_err(git_err("read HEAD"))?
171 .symbolic_target()
172 .map_err(|err| StorageError::Checkpoint {
173 op: "read HEAD",
174 detail: err.to_string(),
175 })?
176 .map(str::to_owned)
177 .ok_or_else(|| StorageError::Checkpoint {
178 op: "move HEAD to the base checkpoint",
179 detail: "HEAD names no branch".to_owned(),
180 })?;
181 self.repo
182 .reference(&branch, oid, false, "sprawling: a base checkpoint")
183 .map_err(|err| commit_refused(Some("HEAD"), err))?;
184 }
185 BaseTarget::Filed => {
186 self.repo
187 .reference(
188 &checkpoint_ref(of, oid),
189 oid,
190 true,
191 "sprawling: a base checkpoint",
192 )
193 .map_err(git_err("file a base checkpoint"))?;
194 }
195 }
196 held.reset()
197 .map_err(git_err("release the base checkpoint"))?;
198 committed(oid, of, scopes, files)
199 }
200}
201
202#[cfg(test)]
203#[allow(
204 clippy::unwrap_used,
205 clippy::expect_used,
206 clippy::panic,
207 clippy::indexing_slicing,
208 reason = "test code"
209)]
210mod tests;