Skip to main content

pijul_core/unrecord/
mod.rs

1use crate::apply;
2use crate::change::*;
3use crate::changestore::*;
4use crate::pristine::*;
5use crate::working_copy::WorkingCopy;
6use std::collections::{HashMap, HashSet};
7
8mod working_copy;
9
10#[derive(Error)]
11pub enum UnrecordError<ChangestoreError: std::error::Error + 'static, T: GraphTxnT + TreeTxnT> {
12    #[error("Changestore error: {0}")]
13    Changestore(ChangestoreError),
14    #[error(transparent)]
15    Txn(#[from] TxnErr<T::GraphError>),
16    #[error(transparent)]
17    Tree(#[from] TreeErr<T::TreeError>),
18    #[error(transparent)]
19    Block(#[from] crate::pristine::BlockError<T::GraphError>),
20    #[error(transparent)]
21    InconsistentChange(#[from] crate::pristine::InconsistentChange<T::GraphError>),
22    #[error("Change not in channel: {}", hash.to_base32())]
23    ChangeNotInChannel { hash: ChangeId },
24    #[error("Change {} is depended upon by {}", change_id.to_base32(), dependent.to_base32())]
25    ChangeIsDependedUpon {
26        change_id: ChangeId,
27        dependent: ChangeId,
28    },
29    #[error(transparent)]
30    Missing(#[from] crate::missing_context::MissingError<T::GraphError>),
31    #[error(transparent)]
32    LocalApply(#[from] crate::apply::LocalApplyError<T>),
33    #[error(transparent)]
34    Apply(#[from] crate::apply::ApplyError<ChangestoreError, T>),
35    #[error(transparent)]
36    SmallStr(#[from] crate::small_string::Error),
37}
38
39impl<C: std::error::Error, T: GraphTxnT + TreeTxnT> std::fmt::Debug for UnrecordError<C, T> {
40    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
41        match self {
42            UnrecordError::Changestore(e) => std::fmt::Debug::fmt(e, fmt),
43            UnrecordError::Txn(e) => std::fmt::Debug::fmt(e, fmt),
44            UnrecordError::Tree(e) => std::fmt::Debug::fmt(e, fmt),
45            UnrecordError::Block(e) => std::fmt::Debug::fmt(e, fmt),
46            UnrecordError::InconsistentChange(e) => std::fmt::Debug::fmt(e, fmt),
47            UnrecordError::ChangeNotInChannel { hash } => {
48                write!(fmt, "Change not in channel: {}", hash.to_base32())
49            }
50            UnrecordError::ChangeIsDependedUpon {
51                change_id,
52                dependent,
53            } => write!(
54                fmt,
55                "Change {} is depended upon: {}",
56                change_id.to_base32(),
57                dependent.to_base32()
58            ),
59            UnrecordError::Missing(e) => std::fmt::Debug::fmt(e, fmt),
60            UnrecordError::LocalApply(e) => std::fmt::Debug::fmt(e, fmt),
61            UnrecordError::Apply(e) => std::fmt::Debug::fmt(e, fmt),
62            UnrecordError::SmallStr(e) => std::fmt::Debug::fmt(e, fmt),
63        }
64    }
65}
66
67pub type TouchedInodes = HashSet<(ChangeId, Position<Option<Hash>>)>;
68
69pub fn unrecord<T: MutTxnT, P: ChangeStore>(
70    txn: &mut T,
71    channel: &ChannelRef<T>,
72    changes: &P,
73    hash: &Hash,
74    salt: u64,
75    touched: &mut TouchedInodes,
76) -> Result<bool, UnrecordError<P::Error, T>> {
77    let change_id = if let Some(&h) = txn.get_internal(&hash.into())? {
78        h
79    } else {
80        return Ok(false);
81    };
82    let unused = unused_in_other_channels(txn, channel, change_id)?;
83    let mut channel = channel.write();
84
85    del_channel_changes::<T, P>(txn, &mut channel, change_id)?;
86
87    let change = changes
88        .get_change(hash)
89        .map_err(UnrecordError::Changestore)?;
90
91    unapply(
92        txn,
93        &mut channel,
94        changes,
95        change_id,
96        &change,
97        salt,
98        touched,
99    )?;
100
101    if unused {
102        assert!(txn.get_revdep(&change_id, None)?.is_none());
103        while txn.del_dep(&change_id, None)? {}
104        txn.del_external(&change_id, None)?;
105        txn.del_internal(&hash.into(), None)?;
106        for dep in change.dependencies.iter() {
107            let dep = *txn.get_internal(&dep.into())?.unwrap();
108            txn.del_revdep(&dep, Some(&change_id))?;
109        }
110        Ok(false)
111    } else {
112        Ok(true)
113    }
114}
115
116fn del_channel_changes<
117    T: ChannelMutTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError> + TreeTxnT,
118    P: ChangeStore,
119>(
120    txn: &mut T,
121    channel: &mut T::Channel,
122    change_id: ChangeId,
123) -> Result<(), UnrecordError<P::Error, T>> {
124    let timestamp = if let Some(&ts) = txn.get_changeset(txn.changes(channel), &change_id)? {
125        ts
126    } else {
127        return Err(UnrecordError::ChangeNotInChannel { hash: change_id });
128    };
129    debug!("del_channel_changes {:?}", change_id);
130    for x in txn.iter_revdep(&change_id)? {
131        debug!("revdep {:?}", x);
132        let (p, d) = x?;
133        assert!(*p >= change_id);
134        if *p > change_id {
135            break;
136        }
137        if txn.get_changeset(txn.changes(channel), d)?.is_some() {
138            return Err(UnrecordError::ChangeIsDependedUpon {
139                change_id,
140                dependent: *d,
141            });
142        }
143    }
144
145    txn.del_changes(channel, change_id, timestamp.into())?;
146
147    let tags = txn.tags_mut(channel);
148    txn.del_tags(tags, timestamp.into())?;
149
150    Ok(())
151}
152
153fn unused_in_other_channels<T: TxnT>(
154    txn: &mut T,
155    channel: &ChannelRef<T>,
156    change_id: ChangeId,
157) -> Result<bool, TxnErr<T::GraphError>> {
158    let channel = channel.read();
159    for br in txn.channels(&crate::small_string::SmallString::default())? {
160        let br = br.read();
161        if txn.name(&br) == txn.name(&channel) {
162            continue;
163        }
164        if txn.get_changeset(txn.changes(&br), &change_id)?.is_some() {
165            return Ok(false);
166        }
167    }
168    Ok(true)
169}
170
171fn unapply<
172    T: ChannelMutTxnT + TreeMutTxnT<TreeError = <T as GraphTxnT>::GraphError>,
173    C: ChangeStore,
174>(
175    txn: &mut T,
176    channel: &mut T::Channel,
177    changes: &C,
178    change_id: ChangeId,
179    change: &Change,
180    salt: u64,
181    touched_inodes: &mut TouchedInodes,
182) -> Result<(), UnrecordError<C::Error, T>> {
183    // `clean_inodes` is used to check whether we're seeing this file
184    // for the first time.
185    let mut clean_inodes = HashSet::new();
186    let mut ws = Workspace::default();
187    for change_ in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
188        info!("unrecording {:?}", change_);
189        match *change_ {
190            Atom::EdgeMap(ref newedges) => {
191                touched_inodes.insert((change_id, newedges.inode));
192                unapply_edges(
193                    changes,
194                    txn,
195                    T::graph_mut(channel),
196                    change_id,
197                    newedges,
198                    &mut ws,
199                )?
200            }
201            Atom::NewVertex(ref newvertex) => {
202                touched_inodes.insert((change_id, newvertex.inode));
203                if clean_inodes.insert(newvertex.inode) {
204                    crate::alive::remove_forward_edges(
205                        txn,
206                        T::graph_mut(channel),
207                        internal_pos(txn, &newvertex.inode, change_id)?,
208                    )?
209                }
210                unapply_newvertex::<T, C>(
211                    txn,
212                    T::graph_mut(channel),
213                    change_id,
214                    &mut ws,
215                    newvertex,
216                )?;
217            }
218        }
219    }
220
221    for change in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
222        match change {
223            Atom::EdgeMap(n) => {
224                // If we are restoring a vertex that was deleted by the
225                // patch we are unrecording, remove its zombie status
226                // (extra pseudo-edges) if necessary.
227                remove_zombies_edges::<_, C>(txn, T::graph_mut(channel), &mut ws, change_id, n)?;
228            }
229            Atom::NewVertex(_) => {}
230        }
231    }
232
233    for change_ in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
234        match *change_ {
235            Atom::EdgeMap(ref newedges) if newedges.edges.is_empty() => {}
236            Atom::EdgeMap(ref newedges) if newedges.edges[0].flag.contains(EdgeFlags::FOLDER) => {
237                if newedges.edges[0].flag.contains(EdgeFlags::DELETED) {
238                    working_copy::undo_file_deletion(
239                        txn, changes, channel, change_id, newedges, salt,
240                    )?
241                } else {
242                    working_copy::undo_file_reinsertion::<C, _>(txn, change_id, newedges)?
243                }
244            }
245            Atom::NewVertex(ref new_vertex)
246                if new_vertex.flag.contains(EdgeFlags::FOLDER)
247                    && new_vertex.down_context.is_empty() =>
248            {
249                working_copy::undo_file_addition(txn, change_id, new_vertex)?;
250            }
251            _ => {}
252        }
253    }
254
255    // Check each touched inode for zombieness, and remove files that
256    // aren't zombies anymore.
257    debug!("touched {:?}", touched_inodes);
258    for (change_id_, inode) in touched_inodes.iter() {
259        if *change_id_ != change_id {
260            continue;
261        }
262        // This inode is actually dead if its only alive adjacent
263        // edges are PSEUDO|FOLDER.
264        if let Ok(inode) = internal_pos(txn, inode, change_id) {
265            let channel = T::graph_mut(channel);
266            collect_zombies_pseudo(txn, channel, inode, &mut ws)?;
267            for (v, mut e) in ws.del_edges.drain(..) {
268                if e.flag().contains(EdgeFlags::PARENT) {
269                    if let Ok(u) = txn.find_block_end(channel, e.dest()) {
270                        e -= EdgeFlags::PARENT;
271                        debug!("line {}, del {:?} {:?} {:?}", line!(), u, v, e);
272                        del_graph_with_rev(txn, channel, e.flag(), *u, v, e.introduced_by())?;
273                    }
274                } else {
275                    if let Ok(w) = txn.find_block(channel, e.dest()) {
276                        debug!("line {}, del {:?} {:?} {:?}", line!(), v, w, e);
277                        del_graph_with_rev(txn, channel, e.flag(), v, *w, e.introduced_by())?;
278                    }
279                }
280            }
281            crate::apply::repair_zombies(txn, channel, inode)?;
282        }
283    }
284
285    let mut inodes = crate::apply::clean_obsolete_pseudo_edges(
286        txn,
287        T::graph_mut(channel),
288        &mut ws.apply,
289        change_id,
290    )?;
291    debug!("inodes = {:?}", inodes);
292    collect_missing_contexts(
293        txn,
294        txn.graph(channel),
295        &mut ws.apply,
296        change,
297        change_id,
298        &mut inodes,
299    )?;
300    for i in inodes {
301        debug!("inodes: repair zombie {:?}", i);
302        crate::apply::repair_zombies(txn, T::graph_mut(channel), i)?;
303    }
304    crate::apply::repair_cyclic_paths(txn, T::graph_mut(channel), &mut ws.apply)?;
305    debug!("unapply done");
306    Ok(())
307}
308
309#[derive(Error)]
310pub enum TouchError<WorkingCopyError: std::error::Error + 'static, T: GraphTxnT + TreeTxnT> {
311    #[error(transparent)]
312    Txn(#[from] TxnErr<T::GraphError>),
313    #[error(transparent)]
314    Tree(#[from] TreeErr<T::TreeError>),
315    #[error(transparent)]
316    WorkingCopy(WorkingCopyError),
317}
318
319impl<W: std::error::Error, T: GraphTxnT + TreeTxnT> std::fmt::Debug for TouchError<W, T> {
320    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
321        match self {
322            TouchError::Txn(e) => std::fmt::Debug::fmt(e, fmt),
323            TouchError::Tree(e) => std::fmt::Debug::fmt(e, fmt),
324            TouchError::WorkingCopy(e) => std::fmt::Debug::fmt(e, fmt),
325        }
326    }
327}
328
329pub fn touch_inodes<
330    T: ChannelMutTxnT + TreeMutTxnT<TreeError = <T as GraphTxnT>::GraphError>,
331    W: WorkingCopy,
332>(
333    txn: &mut T,
334    working_copy: &W,
335    touched_inodes: &TouchedInodes,
336) -> Result<(), TouchError<W::Error, T>> {
337    let now = std::time::SystemTime::now();
338    for (change_id, inode) in touched_inodes.iter() {
339        if let Ok(inode) = internal_pos(txn, inode, *change_id)
340            && let Some(inode) = txn.get_revinodes(&inode, None)?
341            && let Some(name) = crate::fs::inode_filename(txn, *inode)?
342        {
343            debug!(
344                "touching file {:?} {:?}",
345                name,
346                now.duration_since(std::time::SystemTime::UNIX_EPOCH)
347                    .unwrap()
348                    .as_millis()
349            );
350            working_copy.touch(&name, now).unwrap_or(())
351        }
352    }
353    Ok(())
354}
355
356#[derive(Default)]
357struct Workspace {
358    up: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
359    down: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
360    parents: HashSet<Vertex<ChangeId>>,
361    del: Vec<SerializedEdge>,
362    apply: crate::apply::Workspace,
363    stack: Vec<Vertex<ChangeId>>,
364    del_edges: Vec<(Vertex<ChangeId>, SerializedEdge)>,
365    must_reintroduce: HashSet<(Vertex<ChangeId>, Vertex<ChangeId>)>,
366    zombies_stack: Vec<(Vertex<ChangeId>, bool, bool)>,
367}
368
369fn unapply_newvertex<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
370    txn: &mut T,
371    channel: &mut T::Graph,
372    change_id: ChangeId,
373    ws: &mut Workspace,
374    new_vertex: &NewVertex<Option<Hash>>,
375) -> Result<(), UnrecordError<C::Error, T>> {
376    let mut pos = Position {
377        change: change_id,
378        pos: new_vertex.start,
379    };
380    debug!("unapply_newvertex = {:?}", new_vertex);
381    while let Ok(&vertex) = txn.find_block(channel, pos) {
382        debug!("vertex = {:?}", vertex);
383        for e in iter_adj_all(txn, channel, vertex)? {
384            let e = e?;
385            debug!("e = {:?}", e);
386            if !e.flag().is_deleted() {
387                if e.flag().is_parent() {
388                    if !e.flag().is_folder() {
389                        let up_v = txn.find_block_end(channel, e.dest())?;
390                        ws.up.insert(*up_v, new_vertex.inode);
391                    }
392                } else {
393                    let down_v = txn.find_block(channel, e.dest())?;
394                    ws.down.insert(*down_v, new_vertex.inode);
395                    if e.flag().is_folder() {
396                        ws.apply.missing_context.files.insert(*down_v);
397                    }
398                }
399            }
400            ws.del.push(*e)
401        }
402        debug!("del = {:#?}", ws.del);
403        ws.up.remove(&vertex);
404        ws.down.remove(&vertex);
405        ws.perform_del::<C, T>(txn, channel, vertex)?;
406        if vertex.end < new_vertex.end {
407            pos.pos = vertex.end
408        }
409    }
410    Ok(())
411}
412
413impl Workspace {
414    fn perform_del<C: ChangeStore, T: GraphMutTxnT + TreeTxnT>(
415        &mut self,
416        txn: &mut T,
417        channel: &mut T::Graph,
418        vertex: Vertex<ChangeId>,
419    ) -> Result<(), UnrecordError<C::Error, T>> {
420        for e in self.del.drain(..) {
421            let (a, b) = if e.flag().is_parent() {
422                (*txn.find_block_end(channel, e.dest())?, vertex)
423            } else {
424                (vertex, *txn.find_block(channel, e.dest())?)
425            };
426            debug!("line {}, del {:?} {:?} {:?}", line!(), a, b, e);
427            del_graph_with_rev(
428                txn,
429                channel,
430                e.flag() - EdgeFlags::PARENT,
431                a,
432                b,
433                e.introduced_by(),
434            )?;
435        }
436        Ok(())
437    }
438}
439
440fn unapply_edges<T: GraphMutTxnT + TreeTxnT, P: ChangeStore>(
441    changes: &P,
442    txn: &mut T,
443    channel: &mut T::Graph,
444    change_id: ChangeId,
445    newedges: &EdgeMap<Option<Hash>>,
446    ws: &mut Workspace,
447) -> Result<(), UnrecordError<P::Error, T>> {
448    debug!("newedges = {:#?}", newedges);
449    let ext: Hash = txn.get_external(&change_id).optional()?.unwrap().into();
450    ws.must_reintroduce.clear();
451    for n in newedges.edges.iter() {
452        let mut source = crate::apply::edge::find_source_vertex(
453            txn,
454            channel,
455            &n.from,
456            change_id,
457            newedges.inode,
458            n.flag,
459            &mut ws.apply,
460        )?;
461        let mut target = crate::apply::edge::find_target_vertex(
462            txn,
463            channel,
464            &n.to,
465            change_id,
466            newedges.inode,
467            n.flag,
468            &mut ws.apply,
469        )?;
470        loop {
471            let intro_ext = n.introduced_by.unwrap_or(ext);
472            let intro = internal(txn, &n.introduced_by, change_id)?.unwrap();
473            if must_reintroduce::<_, _>(
474                txn, channel, changes, source, target, intro_ext, intro, change_id,
475            )? {
476                ws.must_reintroduce.insert((source, target));
477            }
478            if target.end >= n.to.end {
479                break;
480            }
481            source = target;
482            target = *txn
483                .find_block(channel, target.end_pos())
484                .map_err(UnrecordError::from)?;
485            assert_ne!(source, target);
486        }
487    }
488    let reintro = std::mem::take(&mut ws.must_reintroduce);
489    for edge in newedges.edges.iter() {
490        let intro = internal(txn, &edge.introduced_by, change_id)?.unwrap();
491        apply::put_newedge(
492            txn,
493            channel,
494            &mut ws.apply,
495            intro,
496            newedges.inode,
497            &edge.reverse(Some(ext)),
498            |a, b| reintro.contains(&(a, b)),
499            |h| {
500                if h == &ext {
501                    return true;
502                }
503                if edge.previous.contains(EdgeFlags::DELETED) {
504                    // When reintroducing an edge that was deleted,
505                    // check whether the re-introduction patch knows
506                    // about the alive edges around the target.
507                    changes
508                        .knows(edge.introduced_by.as_ref().unwrap_or(&ext), h)
509                        .unwrap()
510                } else {
511                    // When the edge we are re-introducing is not a
512                    // deletion edge, this check isn't actually used: the
513                    // only zombies in that case are from a deleted
514                    // context, and these aren't detected with known
515                    // patches.
516                    true
517                }
518            },
519        )?;
520    }
521    ws.must_reintroduce = reintro;
522    ws.must_reintroduce.clear();
523    Ok(())
524}
525
526#[allow(clippy::too_many_arguments)]
527fn must_reintroduce<T: GraphTxnT + TreeTxnT, C: ChangeStore>(
528    txn: &T,
529    channel: &T::Graph,
530    changes: &C,
531    a: Vertex<ChangeId>,
532    b: Vertex<ChangeId>,
533    intro: Hash,
534    intro_id: ChangeId,
535    current_id: ChangeId,
536) -> Result<bool, UnrecordError<C::Error, T>> {
537    debug!("a = {:?}, b = {:?}", a, b);
538    // does a patch (call it q) other than the one (p) we're
539    // unrecording remove this same edge (a, b, intro) from the graph?
540    // This can happen e.g. if an edge is deleted twice, in which case
541    // this unrecord is likely to introduce a zombie conflict between
542    // p and q.
543    let b_ext = Position {
544        change: txn.get_external(&b.change).optional()?.map(From::from),
545        pos: b.start,
546    };
547    let mut stack = Vec::new();
548    for e in iter_adj_all(txn, channel, a)? {
549        let e = e?;
550        if e.flag().contains(EdgeFlags::PARENT)
551            || e.dest() != b.start_pos()
552            || e.introduced_by().is_root()
553            || e.introduced_by() == current_id
554        {
555            continue;
556        }
557        // Optimisation to avoid opening change files in the vast
558        // majority of cases: if there is an edge `e` parallel to a ->
559        // b introduced by the change that introduced a or b, don't
560        // reinsert a -> b: that edge was removed when introducing
561        // `e`.
562        if a.change == intro_id || b.change == intro_id {
563            return Ok(false);
564        }
565        stack.push(e.introduced_by())
566    }
567    edge_is_in_channel::<_, _>(txn, changes, b_ext, intro, &mut stack)
568}
569
570fn edge_is_in_channel<T: GraphTxnT + TreeTxnT, C: ChangeStore>(
571    txn: &T,
572    changes: &C,
573    pos: Position<Option<Hash>>,
574    introduced_by: Hash,
575    stack: &mut Vec<ChangeId>,
576) -> Result<bool, UnrecordError<C::Error, T>> {
577    let mut visited = HashSet::new();
578    while let Some(s) = stack.pop() {
579        if !visited.insert(s) {
580            continue;
581        }
582        debug!("stack: {:?}", s);
583        for next in changes
584            .change_deletes_position(|c| txn.get_external(&c).ok().map(From::from), s, pos)
585            .map_err(UnrecordError::Changestore)?
586        {
587            if next == introduced_by {
588                return Ok(false);
589            } else if let Some(i) = txn.get_internal(&next.into())? {
590                stack.push(*i)
591            }
592        }
593    }
594    Ok(true)
595}
596
597/// Remove extra pseudo-edges introduced to mark a zombie conflict, if
598/// the conflict is indeed removed by this unrecord.
599fn remove_zombies_edges<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
600    txn: &mut T,
601    channel: &mut T::Graph,
602    ws: &mut Workspace,
603    change_id: ChangeId,
604    newedges: &EdgeMap<Option<Hash>>,
605) -> Result<(), UnrecordError<C::Error, T>> {
606    debug!("remove_zombies_edges, change_id = {:?}", change_id);
607    for edge in newedges.edges.iter() {
608        let mut to = internal_pos(txn, &edge.to.start_pos(), change_id)?;
609        loop {
610            let to_block = *txn.find_block(channel, to)?;
611            collect_zombies(txn, channel, change_id, to_block, ws)?;
612            debug!("remove_zombies_edges = {:#?}", ws.del_edges);
613            for (v, mut e) in ws.del_edges.drain(..) {
614                if e.flag().contains(EdgeFlags::PARENT) {
615                    let u = *txn.find_block_end(channel, e.dest())?;
616                    e -= EdgeFlags::PARENT;
617                    debug!("line {}, del {:?} {:?} {:?}", line!(), u, v, e);
618                    del_graph_with_rev(txn, channel, e.flag(), u, v, e.introduced_by())?;
619                } else {
620                    let w = *txn.find_block(channel, e.dest())?;
621                    debug!("line {}, del {:?} {:?} {:?}", line!(), v, w, e);
622                    del_graph_with_rev(txn, channel, e.flag(), v, w, e.introduced_by())?;
623                }
624            }
625            if to_block.end < edge.to.end {
626                to = to_block.end_pos()
627            } else {
628                break;
629            }
630        }
631        let from = internal_pos(txn, &edge.from, change_id)?;
632        let from_block = *txn.find_block_end(channel, from)?;
633        collect_zombies(txn, channel, change_id, from_block, ws)?;
634    }
635    Ok(())
636}
637
638/// Collect the edges introduced by the patch we're unrecording to
639/// mark zombie conflicts.
640fn collect_zombies<T: GraphTxnT>(
641    txn: &T,
642    channel: &T::Graph,
643    change_id: ChangeId,
644    to_block: Vertex<ChangeId>,
645    ws: &mut Workspace,
646) -> Result<(), BlockError<T::GraphError>> {
647    ws.stack.push(to_block);
648    while let Some(v) = ws.stack.pop() {
649        debug!("collect_zombies, v = {:?}", v);
650        if !ws.parents.insert(v) {
651            debug!("already seen");
652            continue;
653        }
654        for e in iter_adj_all(txn, channel, v)? {
655            let e = e?;
656            debug!("e = {:?}", e);
657
658            if e.introduced_by() != change_id {
659                continue;
660            }
661            if e.flag().contains(EdgeFlags::PARENT) {
662                ws.stack.push(*txn.find_block_end(channel, e.dest())?)
663            } else {
664                ws.stack.push(*txn.find_block(channel, e.dest())?)
665            }
666            if e.introduced_by() == change_id {
667                ws.del_edges.push((v, *e))
668            }
669        }
670    }
671    ws.stack.clear();
672    ws.parents.clear();
673    debug!("zombies collected");
674    Ok(())
675}
676
677/// Collect the paths going through `to` whose vertices' adjacent
678/// edges are all PSEUDO or DELETED
679fn collect_zombies_pseudo<T: GraphTxnT>(
680    txn: &T,
681    channel: &T::Graph,
682    to: Position<ChangeId>,
683    ws: &mut Workspace,
684) -> Result<(), BlockError<T::GraphError>> {
685    // First, collect the paths downwards.
686    if let Ok(to) = txn.find_block(channel, to) {
687        ws.zombies_stack.push((*to, false, false))
688    }
689
690    while let Some((v, alive, on_path)) = ws.zombies_stack.pop() {
691        debug!("collect_zombies_pseudo {:?} {:?} {:?}", v, alive, on_path);
692        if on_path {
693            // Already visited. If not alive, delete PSEUDO edges.
694            if !alive {
695                for e in iter_adj_all(txn, channel, v)? {
696                    let e = e?;
697                    if e.flag().contains(EdgeFlags::PSEUDO) {
698                        ws.del_edges.push((v, *e))
699                    }
700                }
701                if ws.zombies_stack.is_empty() {
702                    debug_assert_eq!(v.start_pos(), to);
703                    // Collect all the pseudo-paths up.
704                    ws.parents.clear();
705                    collect_zombies_up(txn, channel, to, ws)?
706                }
707            }
708            continue;
709        }
710
711        // A vertex cannot be marked alive if it wasn't on the path.
712        assert!(!alive);
713
714        // If the vertex was already visited in another path, pass.
715        if !ws.parents.insert(v) {
716            continue;
717        }
718
719        ws.zombies_stack.push((v, false, true));
720
721        // Else, iterate through all children. If any of them is
722        // alive, mark the entire path as alive. Else, just push onto
723        // the stack and continue the DFS.
724        for e in iter_alive_children(txn, channel, v)? {
725            let e = e?;
726            if e.flag().intersects(EdgeFlags::PARENT | EdgeFlags::DELETED) {
727                continue;
728            }
729            let x = txn.find_block(channel, e.dest())?;
730            if is_alive(txn, channel, x)? {
731                // Mark all edges on the path as alive.
732                for (_, alive, on_path) in ws.zombies_stack.iter_mut() {
733                    if *on_path {
734                        *alive = true
735                    }
736                }
737            } else {
738                ws.zombies_stack.push((*x, false, false))
739            }
740        }
741    }
742    ws.zombies_stack.clear();
743    ws.parents.clear();
744    Ok(())
745}
746
747fn collect_zombies_up<T: GraphTxnT>(
748    txn: &T,
749    channel: &T::Graph,
750    to: Position<ChangeId>,
751    ws: &mut Workspace,
752) -> Result<(), BlockError<T::GraphError>> {
753    if let Ok(&to) = txn.find_block(channel, to) {
754        ws.stack.push(to);
755    }
756
757    while let Some(v) = ws.stack.pop() {
758        debug!("remove_zombies, v = {:?}", v);
759        if !ws.parents.insert(v) {
760            continue;
761        }
762        let del_len = ws.del_edges.len();
763        let stack_len = ws.stack.len();
764        for e in iter_adj_all(txn, channel, v)? {
765            let e = e?;
766            debug!("e = {:?}", e);
767            if e.flag().contains(EdgeFlags::PARENT) {
768                assert!(e.flag().contains(EdgeFlags::FOLDER));
769
770                if !e.flag().intersects(EdgeFlags::PSEUDO | EdgeFlags::DELETED) {
771                    // Neither a pseudo edge nor a deleted edge.
772                    ws.del_edges.truncate(del_len);
773                    ws.stack.truncate(stack_len);
774                    break;
775                }
776                if let Ok(x) = txn.find_block_end(channel, e.dest()) {
777                    ws.stack.push(*x)
778                }
779                if e.flag().contains(EdgeFlags::PSEUDO) {
780                    ws.del_edges.push((v, *e))
781                }
782            }
783        }
784    }
785    ws.stack.clear();
786    ws.parents.clear();
787    Ok(())
788}
789
790/// Same as crate::apply::collect_missing_contexts, but with the
791/// directions reversed.
792fn collect_missing_contexts<T: GraphMutTxnT + TreeTxnT>(
793    txn: &T,
794    channel: &T::Graph,
795    ws: &mut crate::apply::Workspace,
796    change: &Change,
797    change_id: ChangeId,
798    inodes: &mut HashSet<Position<ChangeId>>,
799) -> Result<(), crate::apply::LocalApplyError<T>> {
800    debug!("collect_missing_contexts");
801    inodes.extend(
802        ws.missing_context
803            .unknown_parents
804            .drain(..)
805            .map(|x| internal_pos(txn, &x.2, change_id).unwrap()),
806    );
807    for atom in change.changes.iter().flat_map(|r| r.iter()) {
808        match atom {
809            // NewVertex is already collected when applying.
810            Atom::NewVertex(_) => {}
811            Atom::EdgeMap(n) => {
812                crate::apply::has_missing_edge_context(
813                    txn, channel, change_id, change, n, inodes, true,
814                )?;
815            }
816        }
817    }
818    Ok(())
819}