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(Debug, Error)]
pub enum UnrecordError<
ChangestoreError: std::error::Error + 'static,
TxnError: std::error::Error + 'static,
> {
#[error("Changestore error: {0}")]
Changestore(ChangestoreError),
#[error(transparent)]
Txn(TxnError),
#[error(transparent)]
Block(#[from] crate::pristine::BlockError<TxnError>),
#[error(transparent)]
InconsistentChange(#[from] crate::pristine::InconsistentChange<TxnError>),
#[error("Change not in channel: {}", hash.to_base32())]
ChangeNotInChannel { hash: ChangeId },
#[error("Change not in channel: {}", change_id.to_base32())]
ChangeIsDependedUpon { change_id: ChangeId },
#[error(transparent)]
Missing(#[from] crate::missing_context::MissingError<TxnError>),
#[error(transparent)]
LocalApply(#[from] crate::apply::LocalApplyError<TxnError>),
#[error(transparent)]
Apply(#[from] crate::apply::ApplyError<ChangestoreError, TxnError>),
}
impl<T: std::error::Error + 'static, C: std::error::Error + 'static> std::convert::From<TxnErr<T>>
for UnrecordError<C, T>
{
fn from(t: TxnErr<T>) -> Self {
UnrecordError::Txn(t.0)
}
}
pub fn unrecord<T: MutTxnT, P: ChangeStore>(
txn: &mut T,
channel: &mut ChannelRef<T>,
changes: &P,
hash: &Hash,
) -> Result<bool, UnrecordError<P::Error, T::GraphError>> {
let change = changes
.get_change(hash)
.map_err(UnrecordError::Changestore)?;
let change_id = if let Some(h) = txn.get_internal(*hash)? {
h
} else {
return Ok(false);
};
let unused = unused_in_other_channels(txn, &channel, change_id)?;
let mut channel = channel.r.borrow_mut();
del_channel_changes::<T, P>(txn, &mut channel, change_id)?;
unapply(txn, &mut channel, changes, change_id, &change)?;
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, None)?;
for dep in change.dependencies.iter() {
let dep = txn.get_internal(*dep)?.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>,
P: ChangeStore,
>(
txn: &mut T,
channel: &mut T::Channel,
change_id: ChangeId,
) -> Result<(), UnrecordError<P::Error, T::GraphError>> {
let timestamp = if let Some(ts) = txn.get_changeset(T::changes(channel), change_id)? {
ts
} else {
return Err(UnrecordError::ChangeNotInChannel { hash: change_id });
};
for x in txn.iter_revdep(change_id)? {
let (p, d) = x?;
if p < change_id {
continue;
} else if p > change_id {
break;
}
if txn.get_changeset(T::changes(channel), d)?.is_some() {
return Err(UnrecordError::ChangeIsDependedUpon { change_id });
}
}
txn.del_changes(channel, change_id, timestamp)?;
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.borrow();
for br in txn.iter_channels("")? {
let br = br?;
let br = br.borrow();
if T::name(&br) != T::name(&channel)
&& txn.get_changeset(T::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,
) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
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,
&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[0].flag.contains(EdgeFlags::FOLDER) => {
if newedges.edges[0].flag.contains(EdgeFlags::DELETED) {
working_copy::undo_file_deletion(txn, changes, channel, change_id, newedges)?
} 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<Edge>,
apply: crate::apply::Workspace,
stack: Vec<Vertex<ChangeId>>,
del_edges: Vec<(Vertex<ChangeId>, Edge)>,
}
fn unapply_newvertex<T: GraphMutTxnT, 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::GraphError>> {
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>(
&mut self,
txn: &mut T,
channel: &mut T::Graph,
vertex: Vertex<ChangeId>,
) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
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, C: ChangeStore>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
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, P: ChangeStore>(
changes: &P,
txn: &mut T,
channel: &mut T::Graph,
change_id: ChangeId,
newedges: &EdgeMap<Option<Hash>>,
ws: &mut Workspace,
) -> Result<(), UnrecordError<P::Error, T::GraphError>> {
debug!("newedges = {:#?}", newedges);
let ext = txn.get_external(change_id)?.unwrap();
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)),
|txn, channel, a, b| {
must_reintroduce(txn, channel, changes, a, b, ext, intro, change_id)
},
)?;
}
Ok(())
}
fn must_reintroduce<T: GraphTxnT, 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::GraphError>> {
debug!("a = {:?}, b = {:?}", a, b);
let b_ext = Position {
change: txn.get_external(b.change)?,
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, C: ChangeStore>(
txn: &T,
changes: &C,
pos: Position<Option<Hash>>,
introduced_by: Hash,
stack: &mut Vec<ChangeId>,
) -> Result<bool, UnrecordError<C::Error, T::GraphError>> {
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(), s, pos)
.map_err(UnrecordError::Changestore)?
{
if next == introduced_by {
return Ok(false);
} else if let Some(i) = txn.get_internal(next)? {
stack.push(i)
}
}
}
Ok(true)
}
fn remove_zombies<T: GraphMutTxnT, C: ChangeStore>(
txn: &mut T,
channel: &mut T::Graph,
ws: &mut Workspace,
change_id: ChangeId,
newedges: &EdgeMap<Option<Hash>>,
) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
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.flag -= 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, 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::GraphError>> {
let change_hash = txn.get_external(change_id)?.unwrap();
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)?;
if !is_alive(txn, channel, to)? {
continue;
}
repair_context_nondeleted(
txn,
channel,
ws,
n.inode,
intro,
|h| changes.knows(&change_hash, &h).unwrap(),
&e.reverse(Some(change_hash)),
)?
}
}
Ok(())
}