//! **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 bstr::BString;use git_hash::ObjectId; use crate::mutable::{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(), } }} /// A way to determine if a value should be created or created or updated. In the latter case the previous/// value can be specified to indicate to what extend the previous value matters.#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]pub enum Create { /// Create a ref only. This fails if the ref exists and does not match the desired new value. Only, /// Create or update the reference with the `previous` value being controlling how to deal with existing ref values. /// OrUpdate { /// Interpret… /// * `None` so that existing values do not matter at all. This is the mode that always creates or updates a reference to the /// desired new value. /// * `Some(Target::Peeled(ObjectId::null_sha1())` so that the reference is required to exist even though its value doesn't matter. /// * `Some(value)` so that the reference is required to exist and have the given `value`. previous: Option<Target>, },} impl Create { pub(crate) fn previous_oid(&self) -> Option<ObjectId> { match self { Create::OrUpdate { previous: Some(Target::Peeled(oid)), } => Some(*oid), Create::Only | Create::OrUpdate { .. } => None, } }} /// 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 create mode. /// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes /// and for use in the reflog. mode: Create, /// 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 previous state of the reference. If set, the reference is expected to exist and match the given value. /// If the value is a peeled null-id the reference is expected to exist but the value doesn't matter, neither peeled nor symbolic. /// If `None`, the actual value does not matter. /// /// If a previous ref existed, this value will be filled in automatically and can be accessed /// if the transaction was committed successfully. previous: Option<Target>, /// How to thread the reference log during deletion. log: RefLog, },} impl Change { /// Return references to values that are in common between all variants. pub fn previous_value(&self) -> Option<crate::Target<'_>> { match self { Change::Update { mode: Create::Only, .. } => None, Change::Update { mode: Create::OrUpdate { previous }, .. } | Change::Delete { previous, .. } => previous.as_ref().map(|t| t.borrow()), } }} /// 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 { use bstr::{BString, ByteVec}; use crate::{ transaction::{Change, LogChange, RefEdit, RefLog, Target}, Namespace, PartialName, }; /// 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(PartialName<'_>) -> Option<Target>, make_entry: impl FnMut(usize, RefEdit) -> T, ) -> Result<(), std::io::Error>; /// If `namespace` is not `None`, alter all edit names by prefixing them with the given namespace. /// Note that symbolic reference targets will also be rewritten to point into the namespace instead. fn adjust_namespace(&mut self, namespace: Option<Namespace>); /// 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(PartialName<'_>) -> Option<Target>, make_entry: impl FnMut(usize, RefEdit) -> T, namespace: impl Into<Option<Namespace>>, ) -> Result<(), std::io::Error> { self.adjust_namespace(namespace.into()); 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 '{}' has multiple edits", name), ) }) } } 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(PartialName<'_>) -> 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.to_partial()) { new_edits.push(make_entry( eid, match &mut edit.change { Change::Delete { previous, log: mode } => { let current_mode = *mode; *mode = RefLog::Only; RefEdit { change: Change::Delete { previous: previous.clone(), log: current_mode, }, name: referent, deref: true, } } Change::Update { log, mode: previous, new, } => { let current = std::mem::replace( log, LogChange { message: log.message.clone(), mode: RefLog::Only, force_create_reflog: log.force_create_reflog, }, ); RefEdit { change: Change::Update { mode: previous.clone(), 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 {} rounds, assuming reference cycle", round ), )); } round += 1; first = self.len(); self.extend(new_edits.drain(..)); } } fn adjust_namespace(&mut self, namespace: Option<Namespace>) { if let Some(namespace) = namespace { for entry in self.iter_mut() { let entry = entry.borrow_mut(); entry.name.0 = { let mut new_name = namespace.0.clone(); new_name.push_str(&entry.name.0); new_name }; if let Change::Update { new: Target::Symbolic(ref mut name), .. } = entry.change { name.0 = { let mut new_name = namespace.0.clone(); new_name.push_str(&name.0); new_name }; } } } } }}pub use ext::RefEditsExt;
//! **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 bstr::BString;
use git_hash::ObjectId;
use crate::mutable::{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(),
}
}
}
/// A way to determine if a value should be created or created or updated. In the latter case the previous
/// value can be specified to indicate to what extend the previous value matters.
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub enum Create {
/// Create a ref only. This fails if the ref exists and does not match the desired new value.
Only,
/// Create or update the reference with the `previous` value being controlling how to deal with existing ref values.
///
OrUpdate {
/// Interpret…
/// * `None` so that existing values do not matter at all. This is the mode that always creates or updates a reference to the
/// desired new value.
/// * `Some(Target::Peeled(ObjectId::null_sha1())` so that the reference is required to exist even though its value doesn't matter.
/// * `Some(value)` so that the reference is required to exist and have the given `value`.
previous: Option<Target>,
},
}
impl Create {
pub(crate) fn previous_oid(&self) -> Option<ObjectId> {
match self {
Create::OrUpdate {
previous: Some(Target::Peeled(oid)),
} => Some(*oid),
Create::Only | Create::OrUpdate { .. } => None,
}
}
}
/// 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 create mode.
/// If a ref was existing previously it will be updated to reflect the previous value for bookkeeping purposes
/// and for use in the reflog.
mode: Create,
/// 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 previous state of the reference. If set, the reference is expected to exist and match the given value.
/// If the value is a peeled null-id the reference is expected to exist but the value doesn't matter, neither peeled nor symbolic.
/// If `None`, the actual value does not matter.
///
/// If a previous ref existed, this value will be filled in automatically and can be accessed
/// if the transaction was committed successfully.
previous: Option<Target>,
/// How to thread the reference log during deletion.
log: RefLog,
},
}
impl Change {
/// Return references to values that are in common between all variants.
pub fn previous_value(&self) -> Option<crate::Target<'_>> {
match self {
Change::Update { mode: Create::Only, .. } => None,
Change::Update {
mode: Create::OrUpdate { previous },
..
}
| Change::Delete { previous, .. } => previous.as_ref().map(|t| t.borrow()),
}
}
}
/// 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 {
use bstr::{BString, ByteVec};
use crate::{
transaction::{Change, LogChange, RefEdit, RefLog, Target},
Namespace, PartialName,
};
/// 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(PartialName<'_>) -> Option<Target>,
make_entry: impl FnMut(usize, RefEdit) -> T,
) -> Result<(), std::io::Error>;
/// If `namespace` is not `None`, alter all edit names by prefixing them with the given namespace.
/// Note that symbolic reference targets will also be rewritten to point into the namespace instead.
fn adjust_namespace(&mut self, namespace: Option<Namespace>);
/// 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(PartialName<'_>) -> Option<Target>,
make_entry: impl FnMut(usize, RefEdit) -> T,
namespace: impl Into<Option<Namespace>>,
) -> Result<(), std::io::Error> {
self.adjust_namespace(namespace.into());
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 '{}' has multiple edits", name),
)
})
}
}
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(PartialName<'_>) -> 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.to_partial()) {
new_edits.push(make_entry(
eid,
match &mut edit.change {
Change::Delete { previous, log: mode } => {
let current_mode = *mode;
*mode = RefLog::Only;
RefEdit {
change: Change::Delete {
previous: previous.clone(),
log: current_mode,
},
name: referent,
deref: true,
}
}
Change::Update {
log,
mode: previous,
new,
} => {
let current = std::mem::replace(
log,
LogChange {
message: log.message.clone(),
mode: RefLog::Only,
force_create_reflog: log.force_create_reflog,
},
);
RefEdit {
change: Change::Update {
mode: previous.clone(),
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 {} rounds, assuming reference cycle",
round
),
));
}
round += 1;
first = self.len();
self.extend(new_edits.drain(..));
}
}
fn adjust_namespace(&mut self, namespace: Option<Namespace>) {
if let Some(namespace) = namespace {
for entry in self.iter_mut() {
let entry = entry.borrow_mut();
entry.name.0 = {
let mut new_name = namespace.0.clone();
new_name.push_str(&entry.name.0);
new_name
};
if let Change::Update {
new: Target::Symbolic(ref mut name),
..
} = entry.change
{
name.0 = {
let mut new_name = namespace.0.clone();
new_name.push_str(&name.0);
new_name
};
}
}
}
}
}
}
pub use ext::RefEditsExt;