summaryrefslogtreecommitdiffstats
path: root/vendor/gix-ref/src/transaction
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/gix-ref/src/transaction')
-rw-r--r--vendor/gix-ref/src/transaction/ext.rs133
-rw-r--r--vendor/gix-ref/src/transaction/mod.rs143
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;