1use gix_error::{ErrorExt, ExnResult, Message, ResultExt, message};
2
3use crate::{
4 FullName, FullNameRef, Reference, Target, packed,
5 packed::transaction::buffer_into_transaction,
6 store_impl::{
7 file,
8 file::{
9 Transaction, loose,
10 transaction::{Edit, PackedRefs},
11 },
12 },
13 transaction::{Change, LogChange, PreviousValue, RefEdit, RefEditsExt, RefLog},
14};
15
16impl Transaction<'_, '_> {
17 fn read_existing_ref(
22 store: &file::Store,
23 name: &FullNameRef,
24 packed: Option<&packed::Buffer>,
25 ) -> ExnResult<Option<Reference>> {
26 let loose = store
27 .ref_contents(name)
28 .or_raise_erased(|| message("Could not read existing reference"))?
29 .and_then(|buf| loose::Reference::try_from_path(name.to_owned(), &buf, store.object_hash).ok())
31 .map(Reference::from);
32 match (loose, packed) {
33 (None, Some(packed)) => packed.try_find(name).map(|reference| reference.map(Into::into)),
34 (reference, _) => Ok(reference),
35 }
36 }
37
38 fn lock_ref_and_apply_change(
39 store: &file::Store,
40 lock_fail_mode: gix_lock::acquire::Fail,
41 packed: Option<&packed::Buffer>,
42 change: &mut Edit,
43 direct_to_packed_refs: bool,
44 ) -> ExnResult {
45 use std::io::Write;
46 assert!(
47 change.lock.is_none(),
48 "locks can only be acquired once and it's all or nothing"
49 );
50
51 store
56 .check_windows_device_name(change.update.name.as_ref())
57 .or_raise_erased(|| message("Invalid reference filename"))?;
58
59 let lock = match &mut change.update.change {
60 Change::Delete { expected, .. } => {
61 let (base, relative_path) = store.reference_path_with_base(change.update.name.as_ref());
62 let lock = gix_lock::Marker::acquire_to_hold_resource(
63 base.join(relative_path.as_ref()),
64 lock_fail_mode,
65 Some(base.clone().into_owned()),
66 )?;
67
68 let existing_ref = Self::read_existing_ref(store, change.update.name.as_ref(), packed)?;
69
70 match (&expected, &existing_ref) {
71 (PreviousValue::MustNotExist, _) => {
72 panic!("BUG: MustNotExist constraint makes no sense if references are to be deleted")
73 }
74 (PreviousValue::ExistingMustMatch(_) | PreviousValue::Any, None)
75 | (PreviousValue::MustExist | PreviousValue::Any, Some(_)) => {}
76 (PreviousValue::MustExist | PreviousValue::MustExistAndMatch(_), None) => {
77 return Err(gix_error::not_found("The reference to delete must exist").raise_erased());
78 }
79 (
80 PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous),
81 Some(existing),
82 ) => {
83 let actual = existing.target.clone();
84 if *previous != actual {
85 let context = message!("Expected reference content {previous}");
86 return Err(ReferenceOutOfDate {
87 full_name: change.name(),
88 actual,
89 }
90 .and_raise(context)
91 .erased());
92 }
93 }
94 }
95
96 if let Some(existing) = existing_ref {
98 *expected = PreviousValue::MustExistAndMatch(existing.target);
99 }
100
101 Some(lock)
102 }
103 Change::Update { expected, new, .. } => {
104 let (base, relative_path) = store.reference_path_with_base(change.update.name.as_ref());
105 let obtain_lock = || {
106 gix_lock::File::acquire_to_update_resource(
107 base.join(relative_path.as_ref()),
108 lock_fail_mode,
109 Some(base.clone().into_owned()),
110 )
111 };
112 let mut lock = obtain_lock()?;
113
114 let existing_ref = Self::read_existing_ref(store, change.update.name.as_ref(), packed)?;
115
116 match (&expected, &existing_ref) {
117 (PreviousValue::Any, _)
118 | (PreviousValue::MustExist, Some(_))
119 | (PreviousValue::MustNotExist | PreviousValue::ExistingMustMatch(_), None) => {}
120 (PreviousValue::MustExist, None) => {
121 return Err(gix_error::not_found("The reference to update must exist").raise_erased());
122 }
123 (PreviousValue::MustNotExist, Some(existing)) => {
124 if existing.target != *new {
125 let context = message!("Expected the reference not to exist when writing {new}");
126 return Err(MustNotExist {
127 full_name: change.name(),
128 actual: existing.target.clone(),
129 }
130 .and_raise(context)
131 .erased());
132 }
133 }
134 (
135 PreviousValue::MustExistAndMatch(previous) | PreviousValue::ExistingMustMatch(previous),
136 Some(existing),
137 ) => {
138 if *previous != existing.target {
139 let actual = existing.target.clone();
140 let context = message!("Expected reference content {previous}");
141 return Err(ReferenceOutOfDate {
142 full_name: change.name(),
143 actual,
144 }
145 .and_raise(context)
146 .erased());
147 }
148 }
149
150 (PreviousValue::MustExistAndMatch(previous), None) => {
151 return Err(
152 gix_error::not_found(format!("The reference must exist with content {previous}"))
153 .raise_erased(),
154 );
155 }
156 }
157
158 fn new_would_change_existing(new: &Target, existing: &Target) -> (bool, bool) {
159 match (new, existing) {
160 (Target::Object(new), Target::Object(old)) => (old != new, false),
161 (Target::Symbolic(new), Target::Symbolic(old)) => (old != new, true),
162 (Target::Object(_), _) => (true, false),
163 (Target::Symbolic(_), _) => (true, true),
164 }
165 }
166
167 let (is_effective, is_symbolic) = if let Some(existing) = existing_ref {
168 let (effective, is_symbolic) = new_would_change_existing(new, &existing.target);
169 *expected = PreviousValue::MustExistAndMatch(existing.target);
170 (effective, is_symbolic)
171 } else {
172 (true, matches!(new, Target::Symbolic(_)))
173 };
174
175 let keep_lock_for_loose_source_delete = direct_to_packed_refs && matches!(new, Target::Object(_));
176 if (is_effective && !direct_to_packed_refs) || is_symbolic {
177 lock.with_mut(|file| match new {
178 Target::Object(oid) => writeln!(file, "{oid}"),
179 Target::Symbolic(name) => writeln!(file, "ref: {}", name.0),
180 })
181 .or_raise_erased(|| message("Could not write loose reference"))?;
182 Some(
183 lock.close()
184 .or_raise_erased(|| message("Could not close reference lock"))?,
185 )
186 } else if keep_lock_for_loose_source_delete {
187 Some(
188 lock.close()
189 .or_raise_erased(|| message("Could not close reference lock"))?,
190 )
191 } else {
192 None
193 }
194 }
195 };
196 change.lock = lock;
197 Ok(())
198 }
199}
200
201impl Transaction<'_, '_> {
202 pub fn prepare(
212 self,
213 edits: impl IntoIterator<Item = RefEdit>,
214 ref_files_lock_fail_mode: gix_lock::acquire::Fail,
215 packed_refs_lock_fail_mode: gix_lock::acquire::Fail,
216 ) -> ExnResult<Self> {
217 self.prepare_inner(
218 &mut edits.into_iter(),
219 ref_files_lock_fail_mode,
220 packed_refs_lock_fail_mode,
221 )
222 }
223
224 fn prepare_inner(
227 mut self,
228 edits: &mut dyn Iterator<Item = RefEdit>,
229 ref_files_lock_fail_mode: gix_lock::acquire::Fail,
230 packed_refs_lock_fail_mode: gix_lock::acquire::Fail,
231 ) -> ExnResult<Self> {
232 assert!(self.updates.is_none(), "BUG: Must not call prepare(…) multiple times");
233 let store = self.store;
234 let mut updates: Vec<_> = edits
235 .map(|update| Edit {
236 update,
237 lock: None,
238 parent_index: None,
239 leaf_referent_previous_oid: None,
240 })
241 .collect();
242 updates
243 .pre_process(
244 &mut |name| {
245 let symbolic_refs_are_never_packed = None;
246 store
247 .find_existing_inner(name, symbolic_refs_are_never_packed)
248 .map(|r| r.target)
249 .ok()
250 },
251 &mut |idx, update| Edit {
252 update,
253 lock: None,
254 parent_index: Some(idx),
255 leaf_referent_previous_oid: None,
256 },
257 )
258 .or_raise_erased(|| message("Could not preprocess reference edits"))?;
259
260 let mut maybe_updates_for_packed_refs = match self.packed_refs {
261 PackedRefs::DeletionsAndNonSymbolicUpdates(_)
262 | PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(_) => Some(0_usize),
263 PackedRefs::DeletionsOnly => None,
264 };
265 if maybe_updates_for_packed_refs.is_some()
266 || self.store.packed_refs_path().is_file()
267 || self.store.packed_refs_lock_path().is_file()
268 {
269 let mut edits_for_packed_transaction = Vec::<RefEdit>::new();
270 let mut needs_packed_refs_lookups = false;
271 for edit in &updates {
272 let log_mode = match edit.update.change {
273 Change::Update {
274 log: LogChange { mode, .. },
275 ..
276 } => mode,
277 Change::Delete { log, .. } => log,
278 };
279 if log_mode == RefLog::Only {
280 continue;
281 }
282 let name = match possibly_adjust_name_for_prefixes(edit.update.name.as_ref()) {
283 Some(n) => n,
284 None => continue,
285 };
286 if let Some(ref mut num_updates) = maybe_updates_for_packed_refs
287 && let Change::Update {
288 new: Target::Object(_), ..
289 } = edit.update.change
290 {
291 edits_for_packed_transaction.push(RefEdit {
292 name,
293 ..edit.update.clone()
294 });
295 *num_updates += 1;
296 continue;
297 }
298 match edit.update.change {
299 Change::Update {
300 expected: PreviousValue::ExistingMustMatch(_) | PreviousValue::MustExistAndMatch(_),
301 ..
302 } => needs_packed_refs_lookups = true,
303 Change::Delete { .. } => {
304 edits_for_packed_transaction.push(RefEdit {
305 name,
306 ..edit.update.clone()
307 });
308 }
309 _ => {
310 needs_packed_refs_lookups = true;
311 }
312 }
313 }
314
315 if !edits_for_packed_transaction.is_empty() || needs_packed_refs_lookups {
316 let packed_transaction: Option<_> =
320 if maybe_updates_for_packed_refs.unwrap_or(0) > 0 || self.store.packed_refs_lock_path().is_file() {
321 self.store.packed_transaction(packed_refs_lock_fail_mode)?.into()
323 } else {
324 self.store
327 .assure_packed_refs_uptodate()?
328 .map(|p| {
329 buffer_into_transaction(
330 p,
331 packed_refs_lock_fail_mode,
332 self.store.precompose_unicode,
333 self.store.namespace.clone(),
334 )
335 })
336 .transpose()?
337 };
338 if let Some(transaction) = packed_transaction {
339 self.packed_transaction = Some(match &mut self.packed_refs {
340 PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(f)
341 | PackedRefs::DeletionsAndNonSymbolicUpdates(f) => {
342 transaction.prepare(&mut edits_for_packed_transaction.into_iter(), &**f)?
343 }
344 PackedRefs::DeletionsOnly => transaction
345 .prepare(&mut edits_for_packed_transaction.into_iter(), &gix_object::find::Never)?,
346 });
347 }
348 }
349 }
350
351 for cid in 0..updates.len() {
352 let change = &mut updates[cid];
353 if let Err(err) = Self::lock_ref_and_apply_change(
354 self.store,
355 ref_files_lock_fail_mode,
356 self.packed_transaction.as_ref().and_then(packed::Transaction::buffer),
357 change,
358 matches!(
359 self.packed_refs,
360 PackedRefs::DeletionsAndNonSymbolicUpdatesRemoveLooseSourceReference(_)
361 ),
362 ) {
363 let referent = change.name();
364 let mut ref_name = referent.clone();
365 let mut cursor = change.parent_index;
366 while let Some(parent_idx) = cursor {
367 let parent = &updates[parent_idx];
368 ref_name = parent.name();
369 cursor = parent.parent_index;
370 }
371 return Err(err
372 .raise(
373 Message::new("Could not prepare reference edit")
374 .with("reference", ref_name)
375 .with("referent", referent),
376 )
377 .erased());
378 }
379
380 if let (Some(crate::TargetRef::Object(oid)), Some(parent_idx)) =
383 (change.update.change.previous_value(), change.parent_index)
384 {
385 let oid = oid.to_owned();
386 let mut parent_idx_cursor = Some(parent_idx);
387 while let Some(parent) = parent_idx_cursor.take().map(|idx| &mut updates[idx]) {
388 parent_idx_cursor = parent.parent_index;
389 parent.leaf_referent_previous_oid = Some(oid);
390 }
391 }
392 }
393 self.updates = Some(updates);
394 Ok(self)
395 }
396
397 pub fn rollback(self) -> Vec<RefEdit> {
406 self.updates
407 .map(|updates| updates.into_iter().map(|u| u.update).collect())
408 .unwrap_or_default()
409 }
410}
411
412fn possibly_adjust_name_for_prefixes(name: &FullNameRef) -> Option<FullName> {
413 match name.category_and_short_name() {
414 Some((c, sn)) => {
415 use crate::Category::*;
416 let sn = FullNameRef::new_unchecked(sn);
417 match c {
418 Bisect | Rewritten | WorktreePrivate | LinkedPseudoRef { .. } | PseudoRef | MainPseudoRef => None,
419 Tag | LocalBranch | RemoteBranch | Note => name.into(),
420 MainRef | LinkedRef { .. } => sn
421 .category()
422 .is_some_and(|cat| !cat.is_worktree_private())
423 .then_some(sn),
424 }
425 .map(ToOwned::to_owned)
426 }
427 None => Some(name.to_owned()), }
429}
430
431#[derive(Debug)]
434pub struct ReferenceOutOfDate {
435 pub full_name: crate::bstr::BString,
437 pub actual: Target,
439}
440
441impl std::fmt::Display for ReferenceOutOfDate {
442 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
443 write!(f, "The reference {:?} changed to {}", self.full_name, self.actual)
444 }
445}
446
447impl std::error::Error for ReferenceOutOfDate {}
448
449#[derive(Debug)]
451pub struct MustNotExist {
452 pub full_name: crate::bstr::BString,
454 pub actual: Target,
456}
457
458impl std::fmt::Display for MustNotExist {
459 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
460 write!(
461 f,
462 "The reference {:?} already exists with content {}",
463 self.full_name, self.actual
464 )
465 }
466}
467
468impl std::error::Error for MustNotExist {}