diff options
Diffstat (limited to 'vendor/gix-ref/src/transaction')
-rw-r--r-- | vendor/gix-ref/src/transaction/ext.rs | 133 | ||||
-rw-r--r-- | vendor/gix-ref/src/transaction/mod.rs | 143 |
2 files changed, 276 insertions, 0 deletions
diff --git a/vendor/gix-ref/src/transaction/ext.rs b/vendor/gix-ref/src/transaction/ext.rs new file mode 100644 index 000000000..cb85d9d17 --- /dev/null +++ b/vendor/gix-ref/src/transaction/ext.rs @@ -0,0 +1,133 @@ +use gix_object::bstr::BString; + +use crate::{ + transaction::{Change, LogChange, PreviousValue, RefEdit, RefLog, Target}, + PartialNameRef, +}; + +/// An extension trait to perform commonly used operations on edits across different ref stores. +pub trait RefEditsExt<T> +where + T: std::borrow::Borrow<RefEdit> + std::borrow::BorrowMut<RefEdit>, +{ + /// Return true if each ref `name` has exactly one `edit` across multiple ref edits + fn assure_one_name_has_one_edit(&self) -> Result<(), BString>; + + /// Split all symbolic refs into updates for the symbolic ref as well as all their referents if the `deref` flag is enabled. + /// + /// Note no action is performed if deref isn't specified. + fn extend_with_splits_of_symbolic_refs( + &mut self, + find: impl FnMut(&PartialNameRef) -> Option<Target>, + make_entry: impl FnMut(usize, RefEdit) -> T, + ) -> Result<(), std::io::Error>; + + /// All processing steps in one and in the correct order. + /// + /// Users call this to assure derefs are honored and duplicate checks are done. + fn pre_process( + &mut self, + find: impl FnMut(&PartialNameRef) -> Option<Target>, + make_entry: impl FnMut(usize, RefEdit) -> T, + ) -> Result<(), std::io::Error> { + self.extend_with_splits_of_symbolic_refs(find, make_entry)?; + self.assure_one_name_has_one_edit().map_err(|name| { + std::io::Error::new( + std::io::ErrorKind::AlreadyExists, + format!("A reference named '{name}' has multiple edits"), + ) + }) + } +} + +impl<E> RefEditsExt<E> for Vec<E> +where + E: std::borrow::Borrow<RefEdit> + std::borrow::BorrowMut<RefEdit>, +{ + fn assure_one_name_has_one_edit(&self) -> Result<(), BString> { + let mut names: Vec<_> = self.iter().map(|e| &e.borrow().name).collect(); + names.sort(); + match names.windows(2).find(|v| v[0] == v[1]) { + Some(name) => Err(name[0].as_bstr().to_owned()), + None => Ok(()), + } + } + + fn extend_with_splits_of_symbolic_refs( + &mut self, + mut find: impl FnMut(&PartialNameRef) -> Option<Target>, + mut make_entry: impl FnMut(usize, RefEdit) -> E, + ) -> Result<(), std::io::Error> { + let mut new_edits = Vec::new(); + let mut first = 0; + let mut round = 1; + loop { + for (eid, edit) in self[first..].iter_mut().enumerate().map(|(eid, v)| (eid + first, v)) { + let edit = edit.borrow_mut(); + if !edit.deref { + continue; + }; + + // we can't tell what happened and we are here because it's a non-existing ref or an invalid one. + // In any case, we don't want the following algorithms to try dereffing it and assume they deal with + // broken refs gracefully. + edit.deref = false; + if let Some(Target::Symbolic(referent)) = find(edit.name.as_ref().as_partial_name()) { + new_edits.push(make_entry( + eid, + match &mut edit.change { + Change::Delete { + expected: previous, + log: mode, + } => { + let current_mode = *mode; + *mode = RefLog::Only; + RefEdit { + change: Change::Delete { + expected: previous.clone(), + log: current_mode, + }, + name: referent, + deref: true, + } + } + Change::Update { log, expected, new } => { + let current = std::mem::replace( + log, + LogChange { + message: log.message.clone(), + mode: RefLog::Only, + force_create_reflog: log.force_create_reflog, + }, + ); + let next = std::mem::replace(expected, PreviousValue::Any); + RefEdit { + change: Change::Update { + expected: next, + new: new.clone(), + log: current, + }, + name: referent, + deref: true, + } + } + }, + )); + } + } + if new_edits.is_empty() { + break Ok(()); + } + if round == 5 { + break Err(std::io::Error::new( + std::io::ErrorKind::WouldBlock, + format!("Could not follow all splits after {round} rounds, assuming reference cycle"), + )); + } + round += 1; + first = self.len(); + + self.append(&mut new_edits); + } + } +} diff --git a/vendor/gix-ref/src/transaction/mod.rs b/vendor/gix-ref/src/transaction/mod.rs new file mode 100644 index 000000000..d13ff2e70 --- /dev/null +++ b/vendor/gix-ref/src/transaction/mod.rs @@ -0,0 +1,143 @@ +//! **Transactions** are the only way make changes to the ref store in order to increase the chance of consistency in a multi-threaded +//! environment. +//! +//! Transactions currently allow to… +//! +//! * create or update reference +//! * delete references +//! +//! The following guarantees are made: +//! +//! * transactions are prepared which is when other writers are prevented from changing them +//! - errors during preparations will cause a perfect rollback +//! * prepared transactions are committed to finalize the change +//! - errors when committing while leave the ref store in an inconsistent, but operational state. +use gix_object::bstr::BString; + +use crate::{FullName, Target}; + +/// A change to the reflog. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct LogChange { + /// How to treat the reference log. + pub mode: RefLog, + /// If set, create a reflog even though it would otherwise not be the case as prohibited by general rules. + /// Note that ref-log writing might be prohibited in the entire repository which is when this flag has no effect either. + pub force_create_reflog: bool, + /// The message to put into the reference log. It must be a single line, hence newlines are forbidden. + /// The string can be empty to indicate there should be no message at all. + pub message: BString, +} + +impl Default for LogChange { + fn default() -> Self { + LogChange { + mode: RefLog::AndReference, + force_create_reflog: false, + message: Default::default(), + } + } +} + +/// The desired value of an updated value +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum PreviousValue { + /// No requirements are made towards the current value, and the new value is set unconditionally. + Any, + /// The reference must exist and may have any value. + MustExist, + /// Create the ref only, hence the reference must not exist. + MustNotExist, + /// The ref _must_ exist and have the given value. + MustExistAndMatch(Target), + /// The ref _may_ exist and have the given value, or may not exist at all. + ExistingMustMatch(Target), +} + +/// A description of an edit to perform. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub enum Change { + /// If previous is not `None`, the ref must exist and its `oid` must agree with the `previous`, and + /// we function like `update`. + /// Otherwise it functions as `create-or-update`. + Update { + /// The desired change to the reference log. + log: LogChange, + /// The expected value already present in the reference. + /// If a ref was existing previously it will be overwritten at `MustExistAndMatch(actual_value)` for use after + /// the transaction was committed successfully. + expected: PreviousValue, + /// The new state of the reference, either for updating an existing one or creating a new one. + new: Target, + }, + /// Delete a reference and optionally check if `previous` is its content. + Delete { + /// The expected value of the reference, with the `MustNotExist` variant being invalid. + /// + /// If a previous ref existed, this value will be filled in automatically as `MustExistAndMatch(actual_value)` and + /// can be accessed if the transaction was committed successfully. + expected: PreviousValue, + /// How to treat the reference log during deletion. + log: RefLog, + }, +} + +impl Change { + /// Return references to values that are the new value after the change is applied, if this is an update. + pub fn new_value(&self) -> Option<crate::TargetRef<'_>> { + match self { + Change::Update { new, .. } => new.to_ref().into(), + Change::Delete { .. } => None, + } + } + + /// Return references to values that are in common between all variants and denote the previous observed value. + pub fn previous_value(&self) -> Option<crate::TargetRef<'_>> { + match self { + // TODO: use or-patterns once MRV is larger than 1.52 (and this is supported) + Change::Update { + expected: PreviousValue::MustExistAndMatch(previous), + .. + } + | Change::Update { + expected: PreviousValue::ExistingMustMatch(previous), + .. + } + | Change::Delete { + expected: PreviousValue::MustExistAndMatch(previous), + .. + } + | Change::Delete { + expected: PreviousValue::ExistingMustMatch(previous), + .. + } => previous, + _ => return None, + } + .to_ref() + .into() + } +} + +/// A reference that is to be changed +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)] +pub struct RefEdit { + /// The change itself + pub change: Change, + /// The name of the reference to apply the change to + pub name: FullName, + /// If set, symbolic references identified by `name` will be dereferenced to have the `change` applied to their target. + /// This flag has no effect if the reference isn't symbolic. + pub deref: bool, +} + +/// The way to deal with the Reflog in deletions. +#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)] +pub enum RefLog { + /// Delete or update the reference and the log + AndReference, + /// Delete or update only the reflog + Only, +} + +mod ext; +pub use ext::RefEditsExt; |