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