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