use crate::apply;
use crate::change::*;
use crate::changestore::*;
use crate::missing_context::*;
use crate::pristine::*;
use std::collections::{HashMap, HashSet};
mod working_copy;
#[derive(Error)]
pub enum UnrecordError<ChangestoreError: std::error::Error + 'static, T: GraphTxnT + TreeTxnT> {
#[error("Changestore error: {0}")]
Changestore(ChangestoreError),
#[error(transparent)]
Txn(#[from] TxnErr<T::GraphError>),
#[error(transparent)]
Tree(#[from] TreeErr<T::TreeError>),
#[error(transparent)]
Block(#[from] crate::pristine::BlockError<T::GraphError>),
#[error(transparent)]
InconsistentChange(#[from] crate::pristine::InconsistentChange<T::GraphError>),
#[error("Change not in channel: {}", hash.to_base32())]
ChangeNotInChannel { hash: ChangeId },
#[error("Change {} is depended upon by {}", change_id.to_base32(), dependent.to_base32())]
ChangeIsDependedUpon {
change_id: ChangeId,
dependent: ChangeId,
},
#[error(transparent)]
Missing(#[from] crate::missing_context::MissingError<T::GraphError>),
#[error(transparent)]
LocalApply(#[from] crate::apply::LocalApplyError<T>),
#[error(transparent)]
Apply(#[from] crate::apply::ApplyError<ChangestoreError, T>),
}
impl<C: std::error::Error, T: GraphTxnT + TreeTxnT> std::fmt::Debug for UnrecordError<C, T> {
fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
match self {
UnrecordError::Changestore(e) => std::fmt::Debug::fmt(e, fmt),
UnrecordError::Txn(e) => std::fmt::Debug::fmt(e, fmt),
UnrecordError::Tree(e) => std::fmt::Debug::fmt(e, fmt),
UnrecordError::Block(e) => std::fmt::Debug::fmt(e, fmt),
UnrecordError::InconsistentChange(e) => std::fmt::Debug::fmt(e, fmt),
UnrecordError::ChangeNotInChannel { hash } => {
write!(fmt, "Change not in channel: {}", hash.to_base32())
}
UnrecordError::ChangeIsDependedUpon {
change_id,
dependent,
} => write!(
fmt,
"Change {} is depended upon: {}",
change_id.to_base32(),
dependent.to_base32()
),
UnrecordError::Missing(e) => std::fmt::Debug::fmt(e, fmt),
UnrecordError::LocalApply(e) => std::fmt::Debug::fmt(e, fmt),
UnrecordError::Apply(e) => std::fmt::Debug::fmt(e, fmt),
}
}
}
pub fn unrecord<T: MutTxnT, P: ChangeStore>(
txn: &mut T,
channel: &ChannelRef<T>,
changes: &P,
hash: &Hash,
salt: u64,
) -> Result<bool, UnrecordError<P::Error, T>> {
let change = changes
.get_change(hash)
.map_err(UnrecordError::Changestore)?;
let change_id = if let Some(&h) = txn.get_internal(&hash.into())? {
h
} else {
return Ok(false);
};
let unused = unused_in_other_channels(txn, &channel, change_id)?;
let mut channel = channel.write();
del_channel_changes::<T, P>(txn, &mut channel, change_id)?;
unapply(txn, &mut channel, changes, change_id, &change, salt)?;
if unused {
assert!(txn.get_revdep(&change_id, None)?.is_none());
while txn.del_dep(&change_id, None)? {}
txn.del_external(&change_id, None)?;
txn.del_internal(&hash.into(), None)?;
for dep in change.dependencies.iter() {
let dep = *txn.get_internal(&dep.into())?.unwrap();
txn.del_revdep(&dep, Some(&change_id))?;
}
Ok(false)
} else {
Ok(true)
}
}
fn del_channel_changes<
T: ChannelMutTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError> + TreeTxnT,
P: ChangeStore,
>(
txn: &mut T,
channel: &mut T::Channel,
change_id: ChangeId,
) -> Result<(), UnrecordError<P::Error, T>> {
let timestamp = if let Some(&ts) = txn.get_changeset(txn.changes(channel), &change_id)? {
ts
} else {
return Err(UnrecordError::ChangeNotInChannel { hash: change_id });
};
debug!("del_channel_changes {:?}", change_id);
for x in txn.iter_revdep(&change_id)? {
debug!("revdep {:?}", x);
let (p, d) = x?;
assert!(*p >= change_id);
if *p > change_id {
break;
}
if txn.get_changeset(txn.changes(channel), d)?.is_some() {
return Err(UnrecordError::ChangeIsDependedUpon {
change_id,
dependent: *d,
});
}
}
txn.del_changes(channel, change_id, timestamp.into())?;
let tags = txn.tags_mut(channel);
txn.del_tags(tags, timestamp.into())?;
Ok(())
}
fn unused_in_other_channels<T: TxnT>(
txn: &mut T,
channel: &ChannelRef<T>,
change_id: ChangeId,
) -> Result<bool, TxnErr<T::GraphError>> {
let channel = channel.read();
for br in txn.channels("")? {
let br = br.read();
if txn.name(&br) == txn.name(&channel) {
continue;
}
if txn.get_changeset(txn.changes(&br), &change_id)?.is_some() {
return Ok(false);
}
}
Ok(true)
}
fn unapply<
T: ChannelMutTxnT + TreeMutTxnT<TreeError = <T as GraphTxnT>::GraphError>,
C: ChangeStore,
>(
txn: &mut T,
channel: &mut T::Channel,
changes: &C,
change_id: ChangeId,
change: &Change,
salt: u64,
) -> Result<(), UnrecordError<C::Error, T>> {
let mut clean_inodes = HashSet::new();
let mut ws = Workspace::default();
for change_ in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
match *change_ {
Atom::EdgeMap(ref newedges) => unapply_edges(
changes,
txn,
T::graph_mut(channel),
change_id,
newedges,
change,
&mut ws,
)?,
Atom::NewVertex(ref newvertex) => {
if clean_inodes.insert(newvertex.inode) {
crate::alive::remove_forward_edges(
txn,
T::graph_mut(channel),
internal_pos(txn, &newvertex.inode, change_id)?,
)?
}
unapply_newvertex::<T, C>(
txn,
T::graph_mut(channel),
change_id,
&mut ws,
newvertex,
)?;
}
}
}
repair_newvertex_contexts::<T, C>(txn, T::graph_mut(channel), &mut ws, change_id)?;
for change in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
if let Atom::EdgeMap(ref n) = *change {
remove_zombies::<_, C>(txn, T::graph_mut(channel), &mut ws, change_id, n)?;
repair_edges_context(
changes,
txn,
T::graph_mut(channel),
&mut ws.apply.missing_context,
change_id,
n,
)?
}
}
for change_ in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
match *change_ {
Atom::EdgeMap(ref newedges) if newedges.edges.is_empty() => {}
Atom::EdgeMap(ref newedges) if newedges.edges[0].flag.contains(EdgeFlags::FOLDER) => {
if newedges.edges[0].flag.contains(EdgeFlags::DELETED) {
working_copy::undo_file_deletion(
txn, changes, channel, change_id, newedges, salt,
)?
} else {
working_copy::undo_file_reinsertion::<C, _>(txn, change_id, newedges)?
}
}
Atom::NewVertex(ref new_vertex)
if new_vertex.flag.contains(EdgeFlags::FOLDER)
&& new_vertex.down_context.is_empty() =>
{
working_copy::undo_file_addition(txn, change_id, new_vertex)?;
}
_ => {}
}
}
crate::apply::clean_obsolete_pseudo_edges(
txn,
T::graph_mut(channel),
&mut ws.apply,
change_id,
)?;
crate::apply::repair_cyclic_paths(txn, T::graph_mut(channel), &mut ws.apply)?;
txn.touch_channel(channel, Some(0));
Ok(())
}
#[derive(Default)]
struct Workspace {
up: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
down: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
parents: HashSet<Vertex<ChangeId>>,
del: Vec<SerializedEdge>,
apply: crate::apply::Workspace,
stack: Vec<Vertex<ChangeId>>,
del_edges: Vec<(Vertex<ChangeId>, SerializedEdge)>,
must_reintroduce: HashSet<(Vertex<ChangeId>, Vertex<ChangeId>)>,
}
fn unapply_newvertex<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
txn: &mut T,
channel: &mut T::Graph,
change_id: ChangeId,
ws: &mut Workspace,
new_vertex: &NewVertex<Option<Hash>>,
) -> Result<(), UnrecordError<C::Error, T>> {
let mut pos = Position {
change: change_id,
pos: new_vertex.start,
};
debug!("unapply_newvertex = {:?}", new_vertex);
while let Ok(&vertex) = txn.find_block(channel, pos) {
debug!("vertex = {:?}", vertex);
for e in iter_adj_all(txn, channel, vertex)? {
let e = e?;
debug!("e = {:?}", e);
if !e.flag().is_deleted() {
if e.flag().is_parent() {
if !e.flag().is_folder() {
let up_v = txn.find_block_end(channel, e.dest())?;
ws.up.insert(*up_v, new_vertex.inode);
}
} else {
let down_v = txn.find_block(channel, e.dest())?;
ws.down.insert(*down_v, new_vertex.inode);
if e.flag().is_folder() {
ws.apply.missing_context.files.insert(*down_v);
}
}
}
ws.del.push(*e)
}
debug!("del = {:#?}", ws.del);
ws.up.remove(&vertex);
ws.down.remove(&vertex);
ws.perform_del::<C, T>(txn, channel, vertex)?;
if vertex.end < new_vertex.end {
pos.pos = vertex.end
}
}
Ok(())
}
impl Workspace {
fn perform_del<C: ChangeStore, T: GraphMutTxnT + TreeTxnT>(
&mut self,
txn: &mut T,
channel: &mut T::Graph,
vertex: Vertex<ChangeId>,
) -> Result<(), UnrecordError<C::Error, T>> {
for e in self.del.drain(..) {
let (a, b) = if e.flag().is_parent() {
(*txn.find_block_end(channel, e.dest())?, vertex)
} else {
(vertex, *txn.find_block(channel, e.dest())?)
};
del_graph_with_rev(
txn,
channel,
e.flag() - EdgeFlags::PARENT,
a,
b,
e.introduced_by(),
)?;
}
Ok(())
}
}
fn repair_newvertex_contexts<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
) -> Result<(), UnrecordError<C::Error, T>> {
debug!("up = {:#?}", ws.up);
for (up, inode) in ws.up.drain() {
if !is_alive(txn, channel, &up)? {
continue;
}
crate::missing_context::repair_missing_down_context(
txn,
channel,
&mut ws.apply.missing_context,
inode,
up,
&[up],
)?
}
debug!("down = {:#?}", ws.down);
for (down, inode) in ws.down.drain() {
if !is_alive(txn, channel, &down)? {
continue;
}
for parent in iter_adjacent(
txn,
channel,
down,
EdgeFlags::PARENT,
EdgeFlags::all() - EdgeFlags::DELETED,
)? {
let parent = parent?;
let parent = txn.find_block_end(channel, parent.dest())?;
if !is_alive(txn, channel, parent)? {
ws.parents.insert(*parent);
}
}
debug!("parents {:#?}", ws.parents);
for up in ws.parents.drain() {
crate::missing_context::repair_missing_up_context(
txn,
channel,
&mut ws.apply.missing_context,
change_id,
inode,
up,
&[down],
)?
}
}
Ok(())
}
fn unapply_edges<T: GraphMutTxnT + TreeTxnT, P: ChangeStore>(
changes: &P,
txn: &mut T,
channel: &mut T::Graph,
change_id: ChangeId,
newedges: &EdgeMap<Option<Hash>>,
change: &Change,
ws: &mut Workspace,
) -> Result<(), UnrecordError<P::Error, T>> {
debug!("newedges = {:#?}", newedges);
let ext: Hash = txn.get_external(&change_id)?.unwrap().into();
ws.must_reintroduce.clear();
for n in newedges.edges.iter() {
let mut source = crate::apply::edge::find_source_vertex(
txn,
channel,
&n.from,
change_id,
newedges.inode,
n.flag,
&mut ws.apply,
)?;
let mut target = crate::apply::edge::find_target_vertex(
txn,
channel,
&n.to,
change_id,
newedges.inode,
n.flag,
&mut ws.apply,
)?;
loop {
let intro_ext = n.introduced_by.unwrap_or(ext);
let intro = internal(txn, &n.introduced_by, change_id)?.unwrap();
if must_reintroduce(
txn, channel, changes, source, target, intro_ext, intro, change_id,
)? {
ws.must_reintroduce.insert((source, target));
}
if target.end >= n.to.end {
break;
}
source = target;
target = *txn
.find_block(channel, target.end_pos())
.map_err(UnrecordError::from)?;
assert_ne!(source, target);
}
}
let reintro = std::mem::take(&mut ws.must_reintroduce);
for edge in newedges.edges.iter() {
let intro = internal(txn, &edge.introduced_by, change_id)?.unwrap();
apply::put_newedge(
txn,
channel,
&mut ws.apply,
intro,
newedges.inode,
&edge.reverse(Some(ext)),
|a, b| reintro.contains(&(a, b)),
|h| change.knows(h),
)?;
}
ws.must_reintroduce = reintro;
ws.must_reintroduce.clear();
Ok(())
}
fn must_reintroduce<T: GraphTxnT + TreeTxnT, C: ChangeStore>(
txn: &T,
channel: &T::Graph,
changes: &C,
a: Vertex<ChangeId>,
b: Vertex<ChangeId>,
intro: Hash,
intro_id: ChangeId,
current_id: ChangeId,
) -> Result<bool, UnrecordError<C::Error, T>> {
debug!("a = {:?}, b = {:?}", a, b);
let b_ext = Position {
change: txn.get_external(&b.change)?.map(From::from),
pos: b.start,
};
let mut stack = Vec::new();
for e in iter_adj_all(txn, channel, a)? {
let e = e?;
if e.flag().contains(EdgeFlags::PARENT)
|| e.dest() != b.start_pos()
|| e.introduced_by().is_root()
|| e.introduced_by() == current_id
{
continue;
}
if a.change == intro_id || b.change == intro_id {
return Ok(false);
}
stack.push(e.introduced_by())
}
edge_is_in_channel(txn, changes, b_ext, intro, &mut stack)
}
fn edge_is_in_channel<T: GraphTxnT + TreeTxnT, C: ChangeStore>(
txn: &T,
changes: &C,
pos: Position<Option<Hash>>,
introduced_by: Hash,
stack: &mut Vec<ChangeId>,
) -> Result<bool, UnrecordError<C::Error, T>> {
let mut visited = HashSet::new();
while let Some(s) = stack.pop() {
if !visited.insert(s) {
continue;
}
debug!("stack: {:?}", s);
for next in changes
.change_deletes_position(|c| txn.get_external(&c).unwrap().map(From::from), s, pos)
.map_err(UnrecordError::Changestore)?
{
if next == introduced_by {
return Ok(false);
} else if let Some(i) = txn.get_internal(&next.into())? {
stack.push(*i)
}
}
}
Ok(true)
}
fn remove_zombies<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
newedges: &EdgeMap<Option<Hash>>,
) -> Result<(), UnrecordError<C::Error, T>> {
debug!("remove_zombies, change_id = {:?}", change_id);
for edge in newedges.edges.iter() {
let to = internal_pos(txn, &edge.to.start_pos(), change_id)?;
collect_zombies(txn, channel, change_id, to, ws)?;
debug!("remove_zombies = {:#?}", ws.del_edges);
for (v, mut e) in ws.del_edges.drain(..) {
if e.flag().contains(EdgeFlags::PARENT) {
let u = *txn.find_block_end(channel, e.dest())?;
e -= EdgeFlags::PARENT;
del_graph_with_rev(txn, channel, e.flag(), u, v, e.introduced_by())?;
} else {
let w = *txn.find_block(channel, e.dest())?;
del_graph_with_rev(txn, channel, e.flag(), v, w, e.introduced_by())?;
}
}
}
Ok(())
}
fn collect_zombies<T: GraphTxnT>(
txn: &T,
channel: &T::Graph,
change_id: ChangeId,
to: Position<ChangeId>,
ws: &mut Workspace,
) -> Result<(), BlockError<T::GraphError>> {
ws.stack.push(*txn.find_block(channel, to)?);
while let Some(v) = ws.stack.pop() {
debug!("remove_zombies, v = {:?}", v);
if !ws.parents.insert(v) {
continue;
}
for e in iter_adj_all(txn, channel, v)? {
let e = e?;
debug!("e = {:?}", e);
if !(e.introduced_by() == change_id || e.flag() & EdgeFlags::bp() == EdgeFlags::PARENT)
{
continue;
}
if e.flag().contains(EdgeFlags::PARENT) {
ws.stack.push(*txn.find_block_end(channel, e.dest())?)
} else {
ws.stack.push(*txn.find_block(channel, e.dest())?)
}
if e.introduced_by() == change_id {
ws.del_edges.push((v, *e))
}
}
}
ws.stack.clear();
ws.parents.clear();
Ok(())
}
fn repair_edges_context<T: GraphMutTxnT + TreeTxnT, P: ChangeStore>(
changes: &P,
txn: &mut T,
channel: &mut T::Graph,
ws: &mut crate::missing_context::Workspace,
change_id: ChangeId,
n: &EdgeMap<Option<Hash>>,
) -> Result<(), UnrecordError<P::Error, T>> {
debug!("repair_edges_context");
let change_hash: Hash = txn.get_external(&change_id)?.unwrap().into();
for e in n.edges.iter() {
assert!(!e.flag.contains(EdgeFlags::PARENT));
let intro = internal(txn, &e.introduced_by, change_id)?.unwrap();
if e.previous.contains(EdgeFlags::DELETED) {
repair_context_deleted(
txn,
channel,
ws,
n.inode,
intro,
|h| changes.knows(&change_hash, &h).unwrap(),
&e.reverse(Some(change_hash)),
)?
} else {
let to = internal_pos(txn, &e.to.start_pos(), change_id)?;
let to = txn.find_block(channel, to)?;
debug!("to = {:?}", to);
if !is_alive(txn, channel, to)? {
debug!("not alive");
continue;
}
repair_context_nondeleted(
txn,
channel,
ws,
n.inode,
intro,
&e.reverse(Some(change_hash)),
)?
}
}
Ok(())
}