use crate::alive::retrieve;
use crate::change::*;
use crate::changestore::ChangeStore;
pub use crate::diff::Algorithm;
use crate::path::{components, Components};
use crate::pristine::*;
use crate::small_string::SmallString;
use crate::working_copy::WorkingCopy;
use crate::{diff, Error};
use std::collections::{HashMap, HashSet};
pub struct Builder {
pub(crate) rec: Recorded,
pub(crate) redundant: Vec<(Vertex<ChangeId>, Edge)>,
recorded_inodes: HashMap<Inode, Position<Option<ChangeId>>>,
deleted_vertices: HashSet<Position<ChangeId>>,
former_parents: Vec<Parent>,
force_rediff: bool,
}
#[derive(Debug)]
struct Parent {
basename: String,
metadata: InodeMetadata,
parent: Position<Option<ChangeId>>,
}
pub struct Recorded {
pub contents: Vec<u8>,
pub actions: Vec<Record<Option<ChangeId>, Local>>,
pub updatables: HashMap<usize, InodeUpdate>,
pub largest_file: u64,
pub has_binary_files: bool,
}
impl Builder {
pub fn new() -> Self {
Builder {
rec: Recorded {
contents: Vec::new(),
actions: Vec::new(),
updatables: HashMap::new(),
has_binary_files: false,
largest_file: 0,
},
redundant: Vec::new(),
recorded_inodes: HashMap::new(),
deleted_vertices: HashSet::new(),
former_parents: Vec::new(),
force_rediff: true,
}
}
pub fn finish(self) -> Recorded {
self.rec
}
}
#[derive(Debug, Hash, PartialEq, Eq)]
pub enum InodeUpdate {
Add {
pos: ChangePosition,
inode: Inode,
},
Deleted {
inode: Inode,
},
}
#[derive(Debug, Clone)]
struct RecordItem<'a> {
v_papa: Position<Option<ChangeId>>,
papa: Inode,
inode: Inode,
basename: String,
full_path: String,
metadata: InodeMetadata,
components: Components<'a>,
}
impl<'a> RecordItem<'a> {
fn root(prefix: &'a str) -> Self {
RecordItem {
inode: Inode::ROOT,
papa: Inode::ROOT,
v_papa: Position::OPTION_ROOT,
basename: String::new(),
full_path: String::new(),
metadata: InodeMetadata::new(0, true),
components: components(prefix),
}
}
}
const CHECK_UTF8: usize = 1000;
fn get_inodes<T: TxnT>(txn: &T, channel: &Channel<T>, inode: Inode) -> Option<Position<ChangeId>> {
if let Some(vertex) = txn.get_inodes(inode, None) {
if txn
.get_changeset(&channel.changes, vertex.change, None)
.is_some()
{
Some(vertex)
} else {
None
}
} else {
None
}
}
impl Builder {
pub(crate) fn record<T: MutTxnT, W: WorkingCopy, C: ChangeStore>(
&mut self,
txn: &mut T,
diff_algorithm: diff::Algorithm,
channel: &mut ChannelRef<T>,
working_copy: &mut W,
changes: &C,
prefix: &str,
) -> Result<(), anyhow::Error> {
let now = std::time::Instant::now();
let channel = channel.borrow();
let mut stack = vec![RecordItem::root(prefix)];
while let Some(mut item) = stack.pop() {
debug!("stack.pop() = Some({:?})", item);
let vertex = if let Some(vertex) = self.recorded_inodes.get(&item.inode) {
*vertex
} else if item.inode == Inode::ROOT {
self.recorded_inodes
.insert(Inode::ROOT, Position::OPTION_ROOT);
self.delete_obsolete_children(
txn,
&channel,
working_copy,
&item.full_path,
Position::ROOT,
);
Position::OPTION_ROOT
} else if let Some(vertex) = get_inodes(txn, &channel, item.inode) {
self.record_existing_file(
txn,
diff_algorithm,
&channel,
working_copy,
changes,
&item,
vertex,
)?;
self.recorded_inodes.insert(item.inode, vertex.to_option());
vertex.to_option()
} else {
match self.add_file(working_copy, item.clone()) {
Ok(Some(vertex)) => {
self.recorded_inodes.insert(item.inode, vertex);
vertex
}
Ok(None) => continue,
Err(_) => {
let parent = txn.get_revtree(item.inode, None).unwrap().to_owned();
info!("recursively deleting {:?} {:?}", parent, item.inode);
crate::fs::rec_delete(txn, parent, item.inode, false)?;
continue;
}
}
};
self.push_children(
txn,
&channel,
working_copy,
&mut item,
vertex,
&mut stack,
prefix,
)?;
}
crate::TIMERS.lock().unwrap().record += now.elapsed();
Ok(())
}
fn add_file<W: WorkingCopy>(
&mut self,
working_copy: &mut W,
item: RecordItem,
) -> Result<Option<Position<Option<ChangeId>>>, anyhow::Error> {
debug!("record_file_addition {:?}", item);
let meta = working_copy.file_metadata(&item.full_path)?;
let name_start = ChangePosition(self.rec.contents.len() as u64);
meta.write(&mut self.rec.contents).unwrap();
self.rec.contents.extend(item.basename.as_bytes());
let name_end = ChangePosition(self.rec.contents.len() as u64);
self.rec.contents.push(0);
let inode_pos = ChangePosition(self.rec.contents.len() as u64);
self.rec.contents.push(0);
let contents = if meta.is_file() {
let start = ChangePosition(self.rec.contents.len() as u64);
working_copy.read_file(&item.full_path, &mut self.rec.contents)?;
let end = ChangePosition(self.rec.contents.len() as u64);
self.rec.largest_file = self.rec.largest_file.max(end.0 - start.0);
self.rec.has_binary_files |= {
let s = start.0 as usize;
let e = (end.0 as usize).min(s + CHECK_UTF8 + 4);
let utf8 = std::str::from_utf8(&self.rec.contents[s..e]);
debug!("utf8 = {:?}", utf8);
match utf8 {
Err(e) => e.valid_up_to() < CHECK_UTF8,
Ok(_) => false,
}
};
self.rec.contents.push(0);
if self.rec.contents.len() as u64 > inode_pos.0 + 1 {
Some(Atom::NewVertex(NewVertex {
up_context: vec![Position {
change: None,
pos: inode_pos,
}],
down_context: vec![],
start,
end,
flag: EdgeFlags::empty(),
inode: Position {
change: None,
pos: inode_pos,
},
}))
} else {
None
}
} else {
None
};
self.rec.actions.push(Record::FileAdd {
add_name: Atom::NewVertex(NewVertex {
up_context: vec![item.v_papa],
down_context: vec![],
start: name_start,
end: name_end,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
inode: item.v_papa,
}),
add_inode: Atom::NewVertex(NewVertex {
up_context: vec![Position {
change: None,
pos: name_end,
}],
down_context: vec![],
start: inode_pos,
end: inode_pos,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
inode: item.v_papa,
}),
contents,
path: item.full_path.clone(),
});
debug!("{:?}", self.rec.actions.last().unwrap());
self.rec.updatables.insert(
self.rec.actions.len(),
InodeUpdate::Add {
inode: item.inode,
pos: inode_pos,
},
);
if meta.is_dir() {
Ok(Some(Position {
change: None,
pos: inode_pos,
}))
} else {
Ok(None)
}
}
fn record_existing_file<T: MutTxnT, W: WorkingCopy, C: ChangeStore>(
&mut self,
txn: &mut T,
diff_algorithm: diff::Algorithm,
channel: &Channel<T>,
working_copy: &mut W,
changes: &C,
item: &RecordItem,
vertex: Position<ChangeId>,
) -> Result<(), anyhow::Error> {
debug!(
"record_existing_file {:?}: {:?} {:?}",
item.full_path, item.inode, vertex
);
self.former_parents.clear();
let f0 = EdgeFlags::FOLDER | EdgeFlags::PARENT;
let f1 = EdgeFlags::all();
let mut is_deleted = true;
for name_ in iter_adjacent(txn, &channel, vertex.inode_vertex(), f0, f1)
.filter(|e| e.flag.contains(EdgeFlags::PARENT))
{
debug!("name_ = {:?}", name_);
if name_.flag.contains(EdgeFlags::DELETED) {
debug!("is_deleted {:?}: {:?}", item.full_path, name_);
is_deleted = true;
break;
}
let name_dest = find_block_end(txn, &channel, name_.dest).unwrap();
let mut name = Vec::new();
changes.get_contents(|p| txn.get_external(p), name_dest, &mut name)?;
let (metadata, basename) = name.split_at(2);
let metadata = InodeMetadata::from_basename(metadata);
let basename = std::str::from_utf8(basename).unwrap().to_string();
debug!("former basename of {:?}: {:?}", vertex, basename);
if let Some(v_papa) = iter_adjacent(txn, &channel, name_dest, f0, f1).next() {
self.former_parents.push(Parent {
basename,
metadata,
parent: v_papa.dest.to_option(),
})
}
}
debug!(
"record_existing_file: {:?} {:?} {:?}",
item, self.former_parents, is_deleted,
);
assert!(!self.former_parents.is_empty());
if let Ok(new_meta) = working_copy.file_metadata(&item.full_path) {
debug!("new_meta = {:?}", new_meta);
if self.former_parents.len() > 1
|| self.former_parents[0].basename != item.basename
|| self.former_parents[0].metadata != item.metadata
|| self.former_parents[0].parent != item.v_papa
|| is_deleted
{
let new_papa = *self.recorded_inodes.get(&item.papa).unwrap();
debug!("new_papa = {:?}", new_papa);
self.record_moved_file(
changes,
txn,
&channel,
&item,
vertex,
new_papa,
self.former_parents[0].metadata,
)?
}
if new_meta.is_file()
&& (self.force_rediff
|| modified_since_last_commit(&channel, working_copy, &item.full_path)?)
{
let mut ret = retrieve(txn, &channel, vertex);
let mut b = Vec::new();
working_copy.read_file(&item.full_path, &mut b)?;
debug!("diffing…");
let len = self.rec.actions.len();
self.diff(
changes,
txn,
&channel,
diff_algorithm,
item.full_path.clone(),
vertex.to_option(),
&mut ret,
&b,
)?;
debug!("new actions: {:?}", &self.rec.actions.len() - len);
}
} else {
debug!("calling record_deleted_file on {:?}", item.full_path);
self.record_deleted_file(txn, &channel, working_copy, &item.full_path, vertex)
}
Ok(())
}
fn delete_obsolete_children<'a, T: MutTxnT, W: WorkingCopy>(
&mut self,
txn: &T,
channel: &Channel<T>,
working_copy: &W,
full_path: &str,
v: Position<ChangeId>,
) {
let f0 = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
let f1 = f0 | EdgeFlags::PSEUDO;
debug!("v = {:?}", v);
for child in iter_adjacent(txn, &channel, v.inode_vertex(), f0, f1) {
let child = find_block(txn, &channel, child.dest).unwrap();
for grandchild in iter_adjacent(txn, &channel, child, f0, f1) {
debug!("grandchild {:?}", grandchild);
let needs_deletion = if let Some(inode) = txn.get_revinodes(grandchild.dest, None) {
debug!("inode = {:?} {:?}", inode, txn.get_revtree(inode, None));
txn.get_revtree(inode, None).is_none()
} else {
true
};
if needs_deletion {
self.record_deleted_file(
txn,
&channel,
working_copy,
full_path,
grandchild.dest,
)
}
}
}
}
fn push_children<'a, T: MutTxnT, W: WorkingCopy>(
&mut self,
txn: &T,
channel: &Channel<T>,
working_copy: &W,
item: &mut RecordItem<'a>,
vertex: Position<Option<ChangeId>>,
stack: &mut Vec<RecordItem<'a>>,
prefix: &str,
) -> Result<(), anyhow::Error> {
let comp = item.components.next();
let full_path = item.full_path.clone();
let fileid = OwnedPathId {
parent_inode: item.inode,
basename: SmallString::new(),
};
let mut has_matching_children = false;
for (fileid_, child_inode) in txn.iter_tree(fileid.clone(), None) {
if fileid_.parent_inode < fileid.parent_inode || fileid_.basename.is_empty() {
continue;
} else if fileid_.parent_inode > fileid.parent_inode {
break;
}
if let Some(comp) = comp {
if comp != fileid_.basename.as_str() {
continue;
}
}
has_matching_children = true;
let basename = fileid_.basename.as_str().to_string();
let full_path = if full_path.is_empty() {
basename.clone()
} else {
full_path.clone() + "/" + &basename
};
debug!("fileid_ {:?} child_inode {:?}", fileid_, child_inode);
if let Ok(meta) = working_copy.file_metadata(&full_path) {
stack.push(RecordItem {
papa: item.inode,
inode: child_inode,
v_papa: vertex,
basename,
full_path,
metadata: meta,
components: item.components.clone(),
})
} else if let Some(vertex) = get_inodes(txn, &channel, child_inode) {
self.record_deleted_file(txn, &channel, working_copy, &full_path, vertex)
}
}
if comp.is_some() && !has_matching_children {
return Err((Error::FileNotInRepo {
path: prefix.to_string(),
})
.into());
}
Ok(())
}
}
fn modified_since_last_commit<T: TxnT, W: WorkingCopy>(
channel: &Channel<T>,
working_copy: &W,
prefix: &str,
) -> Result<bool, anyhow::Error> {
if let Ok(last_modified) = working_copy.modified_time(prefix) {
debug!(
"last_modified = {:?}, channel.last = {:?}",
last_modified
.duration_since(std::time::UNIX_EPOCH)?
.as_secs(),
channel.last_modified
);
Ok(last_modified
.duration_since(std::time::UNIX_EPOCH)?
.as_secs()
+ 2
>= channel.last_modified)
} else {
Ok(true)
}
}
impl Builder {
fn record_moved_file<T: TxnT, C: ChangeStore>(
&mut self,
changes: &C,
txn: &T,
channel: &Channel<T>,
item: &RecordItem,
vertex: Position<ChangeId>,
new_papa: Position<Option<ChangeId>>,
old_meta: InodeMetadata,
) -> Result<(), anyhow::Error> {
debug!("record_moved_file {:?} {:?}", item, old_meta);
let name_start = ChangePosition(self.rec.contents.len() as u64);
item.metadata.write(&mut self.rec.contents)?;
self.rec.contents.extend(item.basename.as_bytes());
let name_end = ChangePosition(self.rec.contents.len() as u64);
self.rec.contents.push(0);
let name = &self.rec.contents[name_start.0 as usize..name_end.0 as usize];
let mut moved = collect_moved_edges(
txn,
changes,
channel,
new_papa,
vertex,
item.metadata,
old_meta,
name,
)?;
debug!("moved = {:#?}", moved);
if !moved.resurrect.is_empty() {
moved.resurrect.extend(moved.alive.into_iter());
self.rec.actions.push(Record::FileUndel {
undel: Atom::EdgeMap(EdgeMap {
edges: moved.resurrect,
inode: item.v_papa,
}),
contents: None,
path: item.full_path.clone(),
});
}
if !moved.edges.is_empty() {
if moved.need_new_name {
self.rec.actions.push(Record::FileMove {
del: Atom::EdgeMap(EdgeMap {
edges: moved.edges,
inode: item.v_papa,
}),
add: Atom::NewVertex(NewVertex {
up_context: vec![item.v_papa],
down_context: vec![vertex.to_option()],
start: name_start,
end: name_end,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
inode: item.v_papa,
}),
path: item.full_path.clone(),
});
} else {
self.rec.actions.push(Record::SolveNameConflict {
name: Atom::EdgeMap(EdgeMap {
edges: moved.edges,
inode: item.v_papa,
}),
path: item.full_path.clone(),
});
self.rec.contents.truncate(name_start.0 as usize)
}
} else {
self.rec.contents.truncate(name_start.0 as usize)
}
Ok(())
}
}
#[derive(Debug)]
struct MovedEdges {
edges: Vec<NewEdge<Option<ChangeId>>>,
alive: Vec<NewEdge<Option<ChangeId>>>,
resurrect: Vec<NewEdge<Option<ChangeId>>>,
need_new_name: bool,
}
fn collect_moved_edges<T: TxnT, C: ChangeStore>(
txn: &T,
changes: &C,
channel: &Channel<T>,
parent_pos: Position<Option<ChangeId>>,
current_pos: Position<ChangeId>,
new_meta: InodeMetadata,
old_meta: InodeMetadata,
name: &[u8],
) -> Result<MovedEdges, anyhow::Error> {
debug!("collect_moved_edges {:?}", current_pos);
let mut moved = MovedEdges {
edges: Vec::new(),
alive: Vec::new(),
resurrect: Vec::new(),
need_new_name: true,
};
let mut del_del = HashMap::new();
let mut alive = HashMap::new();
let mut previous_name = Vec::new();
for parent in iter_adjacent(
txn,
channel,
current_pos.inode_vertex(),
EdgeFlags::FOLDER | EdgeFlags::PARENT,
EdgeFlags::all(),
)
.filter(|e| e.flag.contains(EdgeFlags::FOLDER | EdgeFlags::PARENT))
{
debug!("parent = {:?}", parent);
let mut parent_was_resurrected = false;
if !parent.flag.contains(EdgeFlags::PSEUDO) {
if parent.flag.contains(EdgeFlags::DELETED) {
moved.resurrect.push(NewEdge {
previous: parent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
from: parent.dest.to_option(),
to: current_pos.inode_vertex().to_option(),
introduced_by: Some(parent.introduced_by),
});
parent_was_resurrected = true;
let v = alive
.entry((parent.dest, current_pos.inode_vertex()))
.or_insert(Vec::new());
v.push(None)
} else {
let v = alive
.entry((parent.dest, current_pos.inode_vertex()))
.or_insert(Vec::new());
v.push(Some(parent.introduced_by))
}
}
previous_name.clear();
let parent_dest = find_block_end(txn, &channel, parent.dest).unwrap();
changes.get_contents(|p| txn.get_external(p), parent_dest, &mut previous_name)?;
debug!(
"parent_dest {:?} {:?}",
parent_dest,
std::str::from_utf8(&previous_name[2..])
);
debug!("new_meta = {:?}, old_meta = {:?}", new_meta, old_meta);
let name_changed =
(&previous_name[2..] != &name[2..]) || (new_meta != old_meta && cfg!(not(windows)));
for grandparent in iter_adjacent(
txn,
channel,
parent_dest,
EdgeFlags::FOLDER | EdgeFlags::PARENT,
EdgeFlags::all(),
)
.filter(|e| {
e.flag.contains(EdgeFlags::FOLDER | EdgeFlags::PARENT)
&& !e.flag.contains(EdgeFlags::PSEUDO)
}) {
debug!("grandparent: {:?}", grandparent);
let grandparent_dest = find_block_end(txn, &channel, grandparent.dest).unwrap();
assert_eq!(grandparent_dest.start, grandparent_dest.end);
debug!(
"grandparent_dest {:?} {:?}",
grandparent_dest,
std::str::from_utf8(&previous_name[2..])
);
let grandparent_changed = parent_pos != grandparent.dest.to_option();
debug!("change = {:?}", grandparent_changed || name_changed);
if grandparent.flag.contains(EdgeFlags::DELETED) {
if !grandparent_changed && !name_changed {
moved.resurrect.push(NewEdge {
previous: grandparent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
from: grandparent.dest.to_option(),
to: parent_dest.to_option(),
introduced_by: Some(grandparent.introduced_by),
});
if !parent_was_resurrected && !parent.flag.contains(EdgeFlags::PSEUDO) {
moved.alive.push(NewEdge {
previous: parent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
from: parent.dest.to_option(),
to: current_pos.inode_vertex().to_option(),
introduced_by: Some(parent.introduced_by),
})
}
moved.need_new_name = false
} else {
debug!("cleanup");
let v = del_del
.entry((grandparent.dest, parent_dest))
.or_insert(Vec::new());
v.push(Some(grandparent.introduced_by))
}
} else if grandparent_changed || name_changed {
moved.edges.push(NewEdge {
previous: parent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
from: grandparent.dest.to_option(),
to: parent_dest.to_option(),
introduced_by: Some(grandparent.introduced_by),
});
if !parent_was_resurrected && !parent.flag.contains(EdgeFlags::PSEUDO) {
moved.alive.push(NewEdge {
previous: parent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
from: parent.dest.to_option(),
to: current_pos.inode_vertex().to_option(),
introduced_by: Some(parent.introduced_by),
})
}
} else {
let v = alive
.entry((grandparent.dest, parent_dest))
.or_insert(Vec::new());
v.push(Some(grandparent.introduced_by));
moved.need_new_name = false
}
}
}
for ((from, to), intro) in del_del {
if intro.len() > 1 {
for introduced_by in intro {
if introduced_by.is_some() {
moved.edges.push(NewEdge {
previous: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
from: from.to_option(),
to: to.to_option(),
introduced_by,
})
}
}
}
}
for ((from, to), intro) in alive {
if intro.len() > 1 {
for introduced_by in intro {
if introduced_by.is_some() {
moved.alive.push(NewEdge {
previous: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
from: from.to_option(),
to: to.to_option(),
introduced_by,
})
}
}
}
}
Ok(moved)
}
impl Builder {
fn record_deleted_file<T: TxnT, W: WorkingCopy>(
&mut self,
txn: &T,
channel: &Channel<T>,
working_copy: &W,
full_path: &str,
current_vertex: Position<ChangeId>,
) {
debug!("record_deleted_file {:?} {:?}", current_vertex, full_path);
let mut stack = vec![(current_vertex.inode_vertex(), None)];
let mut visited = HashSet::new();
while let Some((vertex, inode)) = stack.pop() {
debug!("vertex {:?}, inode {:?}", vertex, inode);
if let Some(path) = tree_path(txn, vertex.start_pos()) {
if working_copy.file_metadata(&path).is_ok() {
debug!("not deleting {:?}", path);
continue;
}
}
if let Some(inode) = inode {
self.delete_file_edge(txn, channel, vertex, inode)
} else {
if vertex.start == vertex.end {
debug!("delete_recursively {:?}", vertex);
if !self.deleted_vertices.insert(vertex.start_pos()) {
continue;
}
if let Some(inode) = txn.get_revinodes(vertex.start_pos(), None) {
debug!(
"delete_recursively, vertex = {:?}, inode = {:?}",
vertex, inode
);
self.recorded_inodes
.insert(inode, vertex.start_pos().to_option());
self.rec
.updatables
.insert(self.rec.actions.len(), InodeUpdate::Deleted { inode });
}
self.delete_inode_vertex(txn, channel, vertex, vertex.start_pos(), full_path)
}
}
for edge in iter_adjacent(
txn,
channel,
vertex,
EdgeFlags::empty(),
EdgeFlags::all() - EdgeFlags::DELETED - EdgeFlags::PARENT,
) {
debug!("delete_recursively, edge: {:?}", edge);
let dest =
find_block(txn, &channel, edge.dest).expect("delete_recursively, descendants");
let inode = if inode.is_some() {
assert!(!edge.flag.contains(EdgeFlags::FOLDER));
inode
} else if edge.flag.contains(EdgeFlags::FOLDER) {
None
} else {
assert_eq!(vertex.start, vertex.end);
Some(vertex.start_pos())
};
if visited.insert(edge.dest) {
stack.push((dest, inode))
}
}
}
}
fn delete_inode_vertex<T: TxnT>(
&mut self,
txn: &T,
channel: &Channel<T>,
vertex: Vertex<ChangeId>,
inode: Position<ChangeId>,
path: &str,
) {
let mut edges = Vec::new();
for parent in iter_adjacent(
txn,
channel,
vertex,
EdgeFlags::FOLDER | EdgeFlags::PARENT,
EdgeFlags::all(),
)
.filter(|e| e.flag.contains(EdgeFlags::PARENT))
{
assert!(parent.flag.contains(EdgeFlags::PARENT));
assert!(parent.flag.contains(EdgeFlags::FOLDER));
let parent_dest = find_block_end(txn, &channel, parent.dest).unwrap();
for grandparent in iter_adjacent(
txn,
channel,
parent_dest,
EdgeFlags::FOLDER | EdgeFlags::PARENT,
EdgeFlags::all(),
)
.filter(|e| e.flag.contains(EdgeFlags::PARENT) && !e.flag.contains(EdgeFlags::PSEUDO))
{
assert!(grandparent.flag.contains(EdgeFlags::PARENT));
assert!(grandparent.flag.contains(EdgeFlags::FOLDER));
edges.push(NewEdge {
previous: grandparent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
from: grandparent.dest.to_option(),
to: parent_dest.to_option(),
introduced_by: Some(grandparent.introduced_by),
});
}
if !parent.flag.contains(EdgeFlags::PSEUDO) {
edges.push(NewEdge {
previous: parent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
from: parent.dest.to_option(),
to: vertex.to_option(),
introduced_by: Some(parent.introduced_by),
});
}
}
debug!("deleting {:?}", edges);
if !edges.is_empty() {
self.rec.actions.push(Record::FileDel {
del: Atom::EdgeMap(EdgeMap {
edges: edges,
inode: inode.to_option(),
}),
contents: None,
path: path.to_string(),
})
}
}
fn delete_file_edge<T: TxnT>(
&mut self,
txn: &T,
channel: &Channel<T>,
to: Vertex<ChangeId>,
inode: Position<ChangeId>,
) {
if let Some(Record::FileDel {
ref mut contents, ..
}) = self.rec.actions.last_mut()
{
if contents.is_none() {
*contents = Some(Atom::EdgeMap(EdgeMap {
edges: Vec::new(),
inode: inode.to_option(),
}))
}
if let Some(Atom::EdgeMap(ref mut e)) = *contents {
for parent in iter_adjacent(
txn,
channel,
to,
EdgeFlags::PARENT,
EdgeFlags::all() - EdgeFlags::DELETED,
)
.filter(|e| !e.flag.contains(EdgeFlags::PSEUDO))
{
assert!(parent.flag.contains(EdgeFlags::PARENT));
assert!(!parent.flag.contains(EdgeFlags::FOLDER));
e.edges.push(NewEdge {
previous: parent.flag - EdgeFlags::PARENT,
flag: EdgeFlags::DELETED | EdgeFlags::BLOCK,
from: parent.dest.to_option(),
to: to.to_option(),
introduced_by: Some(parent.introduced_by),
})
}
}
}
}
}