use bstr::BString;
use git_hash::ObjectId;
use crate::mutable::{FullName, Target};
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub struct LogChange {
pub mode: RefLog,
pub force_create_reflog: bool,
pub message: BString,
}
impl Default for LogChange {
fn default() -> Self {
LogChange {
mode: RefLog::AndReference,
force_create_reflog: false,
message: Default::default(),
}
}
}
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub enum Create {
Only,
OrUpdate {
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,
}
}
}
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub enum Change {
Update {
log: LogChange,
mode: Create,
new: Target,
},
Delete {
previous: Option<Target>,
log: RefLog,
},
}
impl Change {
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()),
}
}
}
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone)]
pub struct RefEdit {
pub change: Change,
pub name: FullName,
pub deref: bool,
}
#[derive(PartialEq, Eq, Debug, Hash, Ord, PartialOrd, Clone, Copy)]
pub enum RefLog {
AndReference,
Only,
}
mod ext {
use bstr::{BString, ByteVec};
use crate::{
transaction::{Change, LogChange, RefEdit, RefLog, Target},
Namespace, PartialName,
};
pub trait RefEditsExt<T>
where
T: std::borrow::Borrow<RefEdit> + std::borrow::BorrowMut<RefEdit>,
{
fn assure_one_name_has_one_edit(&self) -> Result<(), BString>;
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>;
fn adjust_namespace(&mut self, namespace: Option<Namespace>);
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;
};
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;