gix_ref/store/packed/
transaction.rs1use std::borrow::Cow;
2use std::{fmt::Formatter, io::Write};
3
4use crate::{
5 file,
6 store_impl::{packed, packed::Edit},
7 transaction::{Change, RefEdit},
8 Namespace, Target,
9};
10
11pub(crate) const HEADER_LINE: &[u8] = b"# pack-refs with: peeled fully-peeled sorted \n";
12
13impl packed::Transaction {
15 pub(crate) fn new_from_pack_and_lock(
16 buffer: Option<file::packed::SharedBufferSnapshot>,
17 lock: gix_lock::File,
18 precompose_unicode: bool,
19 namespace: Option<Namespace>,
20 ) -> Self {
21 packed::Transaction {
22 buffer,
23 edits: None,
24 lock: Some(lock),
25 closed_lock: None,
26 precompose_unicode,
27 namespace,
28 }
29 }
30}
31
32impl std::fmt::Debug for packed::Transaction {
33 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
34 f.debug_struct("packed::Transaction")
35 .field("edits", &self.edits.as_ref().map(Vec::len))
36 .field("lock", &self.lock)
37 .finish_non_exhaustive()
38 }
39}
40
41impl packed::Transaction {
43 pub fn buffer(&self) -> Option<&packed::Buffer> {
45 self.buffer.as_ref().map(|b| &***b)
46 }
47}
48
49impl packed::Transaction {
51 pub fn prepare(
54 mut self,
55 edits: &mut dyn Iterator<Item = RefEdit>,
56 objects: &dyn gix_object::Find,
57 ) -> Result<Self, prepare::Error> {
58 assert!(self.edits.is_none(), "BUG: cannot call prepare(…) more than once");
59 let buffer = &self.buffer;
60 let mut edits: Vec<Edit> = edits
62 .into_iter()
63 .map(|mut edit| {
64 use gix_object::bstr::ByteSlice;
65 if self.precompose_unicode {
66 let precomposed = edit
67 .name
68 .0
69 .to_str()
70 .ok()
71 .map(|name| gix_utils::str::precompose(name.into()));
72 match precomposed {
73 None | Some(Cow::Borrowed(_)) => edit,
74 Some(Cow::Owned(precomposed)) => {
75 edit.name.0 = precomposed.into();
76 edit
77 }
78 }
79 } else {
80 edit
81 }
82 })
83 .map(|mut edit| {
84 if let Some(namespace) = &self.namespace {
85 edit.name = namespace.clone().into_namespaced_name(edit.name.as_ref());
86 }
87 edit
88 })
89 .filter(|edit| {
90 if let Change::Delete { .. } = edit.change {
91 buffer.as_ref().map_or(true, |b| b.find(edit.name.as_ref()).is_ok())
92 } else {
93 true
94 }
95 })
96 .map(|change| Edit {
97 inner: change,
98 peeled: None,
99 })
100 .collect();
101
102 let mut buf = Vec::new();
103 for edit in &mut edits {
104 if let Change::Update {
105 new: Target::Object(new),
106 ..
107 } = edit.inner.change
108 {
109 let mut next_id = new;
110 edit.peeled = loop {
111 let kind = objects.try_find(&next_id, &mut buf)?.map(|d| d.kind);
112 match kind {
113 Some(gix_object::Kind::Tag) => {
114 next_id = gix_object::TagRefIter::from_bytes(&buf).target_id().map_err(|_| {
115 prepare::Error::Resolve(
116 format!("Couldn't get target object id from tag {next_id}").into(),
117 )
118 })?;
119 }
120 Some(_) => {
121 break if next_id == new { None } else { Some(next_id) };
122 }
123 None => {
124 return Err(prepare::Error::Resolve(
125 format!("Couldn't find object with id {next_id}").into(),
126 ))
127 }
128 }
129 };
130 }
131 }
132
133 if edits.is_empty() {
134 self.closed_lock = self
135 .lock
136 .take()
137 .map(gix_lock::File::close)
138 .transpose()
139 .map_err(prepare::Error::CloseLock)?;
140 } else {
141 }
145 self.edits = Some(edits);
146 Ok(self)
147 }
148
149 pub fn commit(self) -> Result<(), commit::Error> {
154 let mut edits = self.edits.expect("BUG: cannot call commit() before prepare(…)");
155 if edits.is_empty() {
156 return Ok(());
157 }
158
159 let mut file = self.lock.expect("a write lock for applying changes");
160 let refs_sorted: Box<dyn Iterator<Item = Result<packed::Reference<'_>, packed::iter::Error>>> =
161 match self.buffer.as_ref() {
162 Some(buffer) => Box::new(buffer.iter()?),
163 None => Box::new(std::iter::empty()),
164 };
165
166 let mut refs_sorted = refs_sorted.peekable();
167
168 edits.sort_by(|l, r| l.inner.name.as_bstr().cmp(r.inner.name.as_bstr()));
169 let mut peekable_sorted_edits = edits.iter().peekable();
170
171 file.with_mut(|f| f.write_all(HEADER_LINE))?;
172
173 let mut num_written_lines = 0;
174 loop {
175 match (refs_sorted.peek(), peekable_sorted_edits.peek()) {
176 (Some(Err(_)), _) => {
177 let err = refs_sorted.next().expect("next").expect_err("err");
178 return Err(commit::Error::Iteration(err));
179 }
180 (None, None) => {
181 break;
182 }
183 (Some(Ok(_)), None) => {
184 let pref = refs_sorted.next().expect("next").expect("no err");
185 num_written_lines += 1;
186 file.with_mut(|out| write_packed_ref(out, pref))?;
187 }
188 (Some(Ok(pref)), Some(edit)) => {
189 use std::cmp::Ordering::*;
190 match pref.name.as_bstr().cmp(edit.inner.name.as_bstr()) {
191 Less => {
192 let pref = refs_sorted.next().expect("next").expect("valid");
193 num_written_lines += 1;
194 file.with_mut(|out| write_packed_ref(out, pref))?;
195 }
196 Greater => {
197 let edit = peekable_sorted_edits.next().expect("next");
198 file.with_mut(|out| write_edit(out, edit, &mut num_written_lines))?;
199 }
200 Equal => {
201 let _pref = refs_sorted.next().expect("next").expect("valid");
202 let edit = peekable_sorted_edits.next().expect("next");
203 file.with_mut(|out| write_edit(out, edit, &mut num_written_lines))?;
204 }
205 }
206 }
207 (None, Some(_)) => {
208 let edit = peekable_sorted_edits.next().expect("next");
209 file.with_mut(|out| write_edit(out, edit, &mut num_written_lines))?;
210 }
211 }
212 }
213
214 if num_written_lines == 0 {
215 std::fs::remove_file(file.resource_path())?;
216 } else {
217 file.commit()?;
218 }
219 drop(refs_sorted);
220 Ok(())
221 }
222}
223
224fn write_packed_ref(out: &mut dyn std::io::Write, pref: packed::Reference<'_>) -> std::io::Result<()> {
225 write!(out, "{} ", pref.target)?;
226 out.write_all(pref.name.as_bstr())?;
227 out.write_all(b"\n")?;
228 if let Some(object) = pref.object {
229 writeln!(out, "^{object}")?;
230 }
231 Ok(())
232}
233
234fn write_edit(out: &mut dyn std::io::Write, edit: &Edit, lines_written: &mut i32) -> std::io::Result<()> {
235 match edit.inner.change {
236 Change::Delete { .. } => {}
237 Change::Update {
238 new: Target::Object(target_oid),
239 ..
240 } => {
241 write!(out, "{target_oid} ")?;
242 out.write_all(edit.inner.name.as_bstr())?;
243 out.write_all(b"\n")?;
244 if let Some(object) = edit.peeled {
245 writeln!(out, "^{object}")?;
246 }
247 *lines_written += 1;
248 }
249 Change::Update {
250 new: Target::Symbolic(_),
251 ..
252 } => unreachable!("BUG: packed refs cannot contain symbolic refs, catch that in prepare(…)"),
253 }
254 Ok(())
255}
256
257pub(crate) fn buffer_into_transaction(
259 buffer: file::packed::SharedBufferSnapshot,
260 lock_mode: gix_lock::acquire::Fail,
261 precompose_unicode: bool,
262 namespace: Option<Namespace>,
263) -> Result<packed::Transaction, gix_lock::acquire::Error> {
264 let lock = gix_lock::File::acquire_to_update_resource(&buffer.path, lock_mode, None)?;
265 Ok(packed::Transaction {
266 buffer: Some(buffer),
267 lock: Some(lock),
268 closed_lock: None,
269 edits: None,
270 precompose_unicode,
271 namespace,
272 })
273}
274
275pub mod prepare {
277 #[derive(Debug, thiserror::Error)]
279 #[allow(missing_docs)]
280 pub enum Error {
281 #[error("Could not close a lock which won't ever be committed")]
282 CloseLock(#[from] std::io::Error),
283 #[error("The lookup of an object failed while peeling it")]
284 Resolve(#[from] Box<dyn std::error::Error + Send + Sync + 'static>),
285 }
286}
287
288pub mod commit {
290 use crate::store_impl::packed;
291
292 #[derive(Debug, thiserror::Error)]
294 #[allow(missing_docs)]
295 pub enum Error {
296 #[error("Changes to the resource could not be committed")]
297 Commit(#[from] gix_lock::commit::Error<gix_lock::File>),
298 #[error("Some references in the packed refs buffer could not be parsed")]
299 Iteration(#[from] packed::iter::Error),
300 #[error("Failed to write a ref line to the packed ref file")]
301 Io(#[from] std::io::Error),
302 }
303}