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            let mut log = crate::apply::ZombieRepairLog::default();
282            crate::apply::repair_zombies(txn, channel, inode, Some((&mut log, change_id)))?;
283            remove_leftover_markings(txn, channel, change_id, &log)?;
284        }
285    }
286
287    let mut inodes = crate::apply::clean_obsolete_pseudo_edges(
288        txn,
289        T::graph_mut(channel),
290        &mut ws.apply,
291        change_id,
292    )?;
293    debug!("inodes = {:?}", inodes);
294    collect_missing_contexts(
295        txn,
296        txn.graph(channel),
297        &mut ws.apply,
298        change,
299        change_id,
300        &mut inodes,
301    )?;
302    for i in inodes {
303        debug!("inodes: repair zombie {:?}", i);
304        let mut log = crate::apply::ZombieRepairLog::default();
305        crate::apply::repair_zombies(txn, T::graph_mut(channel), i, Some((&mut log, change_id)))?;
306        remove_leftover_markings(txn, T::graph_mut(channel), change_id, &log)?;
307    }
308    crate::apply::repair_cyclic_paths(txn, T::graph_mut(channel), &mut ws.apply)?;
309    debug!("unapply done");
310    Ok(())
311}
312
313#[derive(Error)]
314pub enum TouchError<WorkingCopyError: std::error::Error + 'static, T: GraphTxnT + TreeTxnT> {
315    #[error(transparent)]
316    Txn(#[from] TxnErr<T::GraphError>),
317    #[error(transparent)]
318    Tree(#[from] TreeErr<T::TreeError>),
319    #[error(transparent)]
320    WorkingCopy(WorkingCopyError),
321}
322
323impl<W: std::error::Error, T: GraphTxnT + TreeTxnT> std::fmt::Debug for TouchError<W, T> {
324    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
325        match self {
326            TouchError::Txn(e) => std::fmt::Debug::fmt(e, fmt),
327            TouchError::Tree(e) => std::fmt::Debug::fmt(e, fmt),
328            TouchError::WorkingCopy(e) => std::fmt::Debug::fmt(e, fmt),
329        }
330    }
331}
332
333pub fn touch_inodes<
334    T: ChannelMutTxnT + TreeMutTxnT<TreeError = <T as GraphTxnT>::GraphError>,
335    W: WorkingCopy,
336>(
337    txn: &mut T,
338    working_copy: &W,
339    touched_inodes: &TouchedInodes,
340) -> Result<(), TouchError<W::Error, T>> {
341    let now = std::time::SystemTime::now();
342    for (change_id, inode) in touched_inodes.iter() {
343        if let Ok(inode) = internal_pos(txn, inode, *change_id)
344            && let Some(inode) = txn.get_revinodes(&inode, None)?
345            && let Some(name) = crate::fs::inode_filename(txn, *inode)?
346        {
347            debug!(
348                "touching file {:?} {:?}",
349                name,
350                now.duration_since(std::time::SystemTime::UNIX_EPOCH)
351                    .unwrap()
352                    .as_millis()
353            );
354            working_copy.touch(&name, now).unwrap_or(())
355        }
356    }
357    Ok(())
358}
359
360#[derive(Default)]
361struct Workspace {
362    up: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
363    down: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
364    parents: HashSet<Vertex<ChangeId>>,
365    del: Vec<SerializedEdge>,
366    apply: crate::apply::Workspace,
367    stack: Vec<Vertex<ChangeId>>,
368    del_edges: Vec<(Vertex<ChangeId>, SerializedEdge)>,
369    must_reintroduce: HashSet<(Vertex<ChangeId>, Vertex<ChangeId>, ChangeId)>,
370    zombies_stack: Vec<(Vertex<ChangeId>, bool, bool)>,
371}
372
373fn unapply_newvertex<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
374    txn: &mut T,
375    channel: &mut T::Graph,
376    change_id: ChangeId,
377    ws: &mut Workspace,
378    new_vertex: &NewVertex<Option<Hash>>,
379) -> Result<(), UnrecordError<C::Error, T>> {
380    let mut pos = Position {
381        change: change_id,
382        pos: new_vertex.start,
383    };
384    debug!("unapply_newvertex = {:?}", new_vertex);
385    while let Ok(&vertex) = txn.find_block(channel, pos) {
386        debug!("vertex = {:?}", vertex);
387        for e in iter_adj_all(txn, channel, vertex)? {
388            let e = e?;
389            debug!("e = {:?}", e);
390            if !e.flag().is_deleted() {
391                if e.flag().is_parent() {
392                    if !e.flag().is_folder() {
393                        let up_v = txn.find_block_end(channel, e.dest())?;
394                        ws.up.insert(*up_v, new_vertex.inode);
395                    }
396                } else {
397                    let down_v = txn.find_block(channel, e.dest())?;
398                    ws.down.insert(*down_v, new_vertex.inode);
399                    if e.flag().is_folder() {
400                        ws.apply.missing_context.files.insert(*down_v);
401                    }
402                }
403            }
404            ws.del.push(*e)
405        }
406        debug!("del = {:#?}", ws.del);
407        ws.up.remove(&vertex);
408        ws.down.remove(&vertex);
409        ws.perform_del::<C, T>(txn, channel, vertex)?;
410        if vertex.end < new_vertex.end {
411            pos.pos = vertex.end
412        }
413    }
414    Ok(())
415}
416
417impl Workspace {
418    fn perform_del<C: ChangeStore, T: GraphMutTxnT + TreeTxnT>(
419        &mut self,
420        txn: &mut T,
421        channel: &mut T::Graph,
422        vertex: Vertex<ChangeId>,
423    ) -> Result<(), UnrecordError<C::Error, T>> {
424        for e in self.del.drain(..) {
425            let (a, b) = if e.flag().is_parent() {
426                (*txn.find_block_end(channel, e.dest())?, vertex)
427            } else {
428                (vertex, *txn.find_block(channel, e.dest())?)
429            };
430            debug!("line {}, del {:?} {:?} {:?}", line!(), a, b, e);
431            del_graph_with_rev(
432                txn,
433                channel,
434                e.flag() - EdgeFlags::PARENT,
435                a,
436                b,
437                e.introduced_by(),
438            )?;
439        }
440        Ok(())
441    }
442}
443
444fn unapply_edges<T: GraphMutTxnT + TreeTxnT, P: ChangeStore>(
445    changes: &P,
446    txn: &mut T,
447    channel: &mut T::Graph,
448    change_id: ChangeId,
449    newedges: &EdgeMap<Option<Hash>>,
450    ws: &mut Workspace,
451) -> Result<(), UnrecordError<P::Error, T>> {
452    debug!("newedges = {:#?}", newedges);
453    let ext: Hash = txn.get_external(&change_id).optional()?.unwrap().into();
454    ws.must_reintroduce.clear();
455    for n in newedges.edges.iter() {
456        let mut source = crate::apply::edge::find_source_vertex(
457            txn,
458            channel,
459            &n.from,
460            change_id,
461            newedges.inode,
462            n.flag,
463            &mut ws.apply,
464        )?;
465        let mut target = crate::apply::edge::find_target_vertex(
466            txn,
467            channel,
468            &n.to,
469            change_id,
470            newedges.inode,
471            n.flag,
472            &mut ws.apply,
473        )?;
474        loop {
475            let intro_ext = n.introduced_by.unwrap_or(ext);
476            let intro = internal(txn, &n.introduced_by, change_id)?.unwrap();
477            if must_reintroduce::<_, _>(
478                txn, channel, changes, source, target, intro_ext, intro, change_id,
479            )? {
480                ws.must_reintroduce.insert((source, target, intro));
481            }
482            if target.end >= n.to.end {
483                break;
484            }
485            source = target;
486            target = *txn
487                .find_block(channel, target.end_pos())
488                .map_err(UnrecordError::from)?;
489            assert_ne!(source, target);
490        }
491    }
492    let reintro = std::mem::take(&mut ws.must_reintroduce);
493    for edge in newedges.edges.iter() {
494        let intro = internal(txn, &edge.introduced_by, change_id)?.unwrap();
495        apply::put_newedge(
496            txn,
497            channel,
498            &mut ws.apply,
499            intro,
500            newedges.inode,
501            &edge.reverse(Some(ext)),
502            |a, b| reintro.contains(&(a, b, intro)),
503            |h| {
504                if h == &ext {
505                    return true;
506                }
507                if edge.previous.contains(EdgeFlags::DELETED) {
508                    // When reintroducing an edge that was deleted,
509                    // check whether the re-introduction patch knows
510                    // about the alive edges around the target.
511                    changes
512                        .knows(edge.introduced_by.as_ref().unwrap_or(&ext), h)
513                        .unwrap()
514                } else {
515                    // When the edge we are re-introducing is not a
516                    // deletion edge, this check isn't actually used: the
517                    // only zombies in that case are from a deleted
518                    // context, and these aren't detected with known
519                    // patches.
520                    true
521                }
522            },
523        )?;
524    }
525
526    ws.must_reintroduce = reintro;
527    ws.must_reintroduce.clear();
528    Ok(())
529}
530
531#[allow(clippy::too_many_arguments)]
532fn must_reintroduce<T: GraphTxnT + TreeTxnT, C: ChangeStore>(
533    txn: &T,
534    channel: &T::Graph,
535    changes: &C,
536    a: Vertex<ChangeId>,
537    b: Vertex<ChangeId>,
538    intro: Hash,
539    intro_id: ChangeId,
540    current_id: ChangeId,
541) -> Result<bool, UnrecordError<C::Error, T>> {
542    debug!("a = {:?}, b = {:?}", a, b);
543    // does a patch (call it q) other than the one (p) we're
544    // unrecording remove this same edge (a, b, intro) from the graph?
545    // This can happen e.g. if an edge is deleted twice, in which case
546    // this unrecord is likely to introduce a zombie conflict between
547    // p and q.
548    let b_ext = Position {
549        change: txn.get_external(&b.change).optional()?.map(From::from),
550        pos: b.start,
551    };
552    let mut stack = Vec::new();
553    for e in iter_adj_all(txn, channel, a)? {
554        let e = e?;
555        if e.flag().contains(EdgeFlags::PARENT)
556            || e.dest() != b.start_pos()
557            || e.introduced_by().is_root()
558            || e.introduced_by() == current_id
559        {
560            continue;
561        }
562        // Optimisation to avoid opening change files in the vast
563        // majority of cases: if there is an *alive* edge `e` parallel to
564        // a -> b introduced by the change that introduced a or b, don't
565        // reinsert a -> b: that edge was removed when introducing `e`.
566        //
567        // We must not take this shortcut for a DELETED parallel edge: that
568        // is a zombie marking, meaning a -> b was alive *alongside* the
569        // deletion (a zombie), so it does need to be reintroduced. Fall
570        // through to `edge_is_in_channel`, which decides correctly.
571        if !e.flag().is_deleted() && (a.change == intro_id || b.change == intro_id) {
572            return Ok(false);
573        }
574        stack.push(e.introduced_by())
575    }
576    edge_is_in_channel::<_, _>(txn, changes, b_ext, intro, &mut stack)
577}
578
579fn edge_is_in_channel<T: GraphTxnT + TreeTxnT, C: ChangeStore>(
580    txn: &T,
581    changes: &C,
582    pos: Position<Option<Hash>>,
583    introduced_by: Hash,
584    stack: &mut Vec<ChangeId>,
585) -> Result<bool, UnrecordError<C::Error, T>> {
586    let mut visited = HashSet::new();
587    while let Some(s) = stack.pop() {
588        if !visited.insert(s) {
589            continue;
590        }
591        debug!("stack: {:?}", s);
592        for next in changes
593            .change_deletes_position(|c| txn.get_external(&c).ok().map(From::from), s, pos)
594            .map_err(UnrecordError::Changestore)?
595        {
596            if next == introduced_by {
597                return Ok(false);
598            } else if let Some(i) = txn.get_internal(&next.into())? {
599                stack.push(*i)
600            }
601        }
602    }
603    Ok(true)
604}
605
606/// Remove extra pseudo-edges introduced to mark a zombie conflict, if
607/// the conflict is indeed removed by this unrecord.
608fn remove_zombies_edges<T: GraphMutTxnT + TreeTxnT, C: ChangeStore>(
609    txn: &mut T,
610    channel: &mut T::Graph,
611    ws: &mut Workspace,
612    change_id: ChangeId,
613    newedges: &EdgeMap<Option<Hash>>,
614) -> Result<(), UnrecordError<C::Error, T>> {
615    debug!("remove_zombies_edges, change_id = {:?}", change_id);
616    for edge in newedges.edges.iter() {
617        let mut to = internal_pos(txn, &edge.to.start_pos(), change_id)?;
618        loop {
619            let to_block = *txn.find_block(channel, to)?;
620            collect_zombies(txn, channel, change_id, to_block, ws)?;
621            collect_zombies_context(txn, channel, change_id, to_block, ws)?;
622            debug!("remove_zombies_edges = {:#?}", ws.del_edges);
623            if to_block.end < edge.to.end {
624                to = to_block.end_pos()
625            } else {
626                break;
627            }
628        }
629        let from = internal_pos(txn, &edge.from, change_id)?;
630        let from_block = *txn.find_block_end(channel, from)?;
631        collect_zombies(txn, channel, change_id, from_block, ws)?;
632        collect_zombies_context(txn, channel, change_id, from_block, ws)?;
633
634        for (v, mut e) in ws.del_edges.drain(..) {
635            if e.flag().contains(EdgeFlags::PARENT) {
636                let u = *txn.find_block_end(channel, e.dest())?;
637                e -= EdgeFlags::PARENT;
638                debug!("line {}, del {:?} {:?} {:?}", line!(), u, v, e);
639                del_graph_with_rev(txn, channel, e.flag(), u, v, e.introduced_by())?;
640            } else {
641                let w = *txn.find_block(channel, e.dest())?;
642                debug!("line {}, del {:?} {:?} {:?}", line!(), v, w, e);
643                del_graph_with_rev(txn, channel, e.flag(), v, w, e.introduced_by())?;
644            }
645        }
646    }
647    Ok(())
648}
649
650/// Collect the edges introduced by the patch we're unrecording to
651/// mark zombie conflicts.
652fn collect_zombies<T: GraphTxnT>(
653    txn: &T,
654    channel: &T::Graph,
655    change_id: ChangeId,
656    to_block: Vertex<ChangeId>,
657    ws: &mut Workspace,
658) -> Result<(), BlockError<T::GraphError>> {
659    ws.stack.push(to_block);
660    while let Some(v) = ws.stack.pop() {
661        debug!("collect_zombies, v = {:?}", v);
662        if !ws.parents.insert(v) {
663            debug!("already seen");
664            continue;
665        }
666        for e in iter_adj_all(txn, channel, v)? {
667            let e = e?;
668            debug!("e = {:?}", e);
669
670            if e.introduced_by() != change_id {
671                continue;
672            }
673            if e.flag().contains(EdgeFlags::PARENT) {
674                ws.stack.push(*txn.find_block_end(channel, e.dest())?)
675            } else {
676                ws.stack.push(*txn.find_block(channel, e.dest())?)
677            }
678            if e.introduced_by() == change_id {
679                ws.del_edges.push((v, *e))
680            } else {
681                // break
682            }
683        }
684    }
685    ws.stack.clear();
686    ws.parents.clear();
687    debug!("zombies collected");
688    Ok(())
689}
690
691/// When `change_id` deleted `v` and `v`'s context belonged to a patch that
692/// `change_id` did not know about, `apply::edge::zombify` marked that context as
693/// a zombie by attaching DELETED edges (introduced by `change_id`) to it. That
694/// context is reachable from the deleted vertex only through *non-BLOCK* edges
695/// (BLOCK edges stay within a single change), so this walks up the non-BLOCK
696/// context edges from `v` and collects the DELETED markings that `change_id`
697/// left on the vertices reached. Following the alive non-BLOCK context is what
698/// lets us step across the *other* change into the marked vertices — something
699/// `collect_zombies` (which only follows `change_id` edges) cannot do.
700fn collect_zombies_context<T: GraphTxnT>(
701    txn: &T,
702    channel: &T::Graph,
703    change_id: ChangeId,
704    v: Vertex<ChangeId>,
705    ws: &mut Workspace,
706) -> Result<(), BlockError<T::GraphError>> {
707    let mut stack = vec![v];
708    let mut visited = HashSet::new();
709    while let Some(w) = stack.pop() {
710        if !visited.insert(w) {
711            continue;
712        }
713        for e in iter_adj_all(txn, channel, w)? {
714            let e = e?;
715            if e.flag().contains(EdgeFlags::FOLDER) {
716                continue;
717            }
718            // A zombie marking `zombify` left on a context vertex. It always
719            // writes DELETED|BLOCK edges (BLOCK asserts the vertex is dead); the
720            // plain-DELETED markings `put_newedge` adds for a *later* insertion
721            // landing on deleted context are ordering edges, not liveness, and
722            // must not be stripped here. We only collect these on the context
723            // vertices we reach, never on the deleted vertex `v` itself, which
724            // zombify never marks.
725            if w != v
726                && e.introduced_by() == change_id
727                && e.flag().is_deleted()
728                && e.flag().contains(EdgeFlags::BLOCK)
729            {
730                ws.del_edges.push((w, *e));
731            }
732            // Walk up towards the marked context, but only along *alive*,
733            // non-BLOCK parent edges. Those are the ordering/context edges that
734            // cross into the other change; stepping across a marking edge would
735            // wander into an unrelated zombie chain belonging to another change.
736            if e.flag().contains(EdgeFlags::PARENT) && !e.flag().contains(EdgeFlags::BLOCK) {
737                stack.push(*txn.find_block_end(channel, e.dest())?);
738            }
739        }
740    }
741    Ok(())
742}
743
744/// Collect the paths going through `to` whose vertices' adjacent
745/// edges are all PSEUDO or DELETED
746fn collect_zombies_pseudo<T: GraphTxnT>(
747    txn: &T,
748    channel: &T::Graph,
749    to: Position<ChangeId>,
750    ws: &mut Workspace,
751) -> Result<(), BlockError<T::GraphError>> {
752    // First, collect the paths downwards.
753    if let Ok(to) = txn.find_block(channel, to) {
754        ws.zombies_stack.push((*to, false, false))
755    }
756
757    while let Some((v, alive, on_path)) = ws.zombies_stack.pop() {
758        debug!("collect_zombies_pseudo {:?} {:?} {:?}", v, alive, on_path);
759        if on_path {
760            // Already visited. If not alive, delete PSEUDO edges.
761            if !alive {
762                for e in iter_adj_all(txn, channel, v)? {
763                    let e = e?;
764                    if e.flag().contains(EdgeFlags::PSEUDO) {
765                        ws.del_edges.push((v, *e))
766                    }
767                }
768                if ws.zombies_stack.is_empty() {
769                    debug_assert_eq!(v.start_pos(), to);
770                    // Collect all the pseudo-paths up.
771                    ws.parents.clear();
772                    collect_zombies_up(txn, channel, to, ws)?
773                }
774            }
775            continue;
776        }
777
778        // A vertex cannot be marked alive if it wasn't on the path.
779        assert!(!alive);
780
781        // If the vertex was already visited in another path, pass.
782        if !ws.parents.insert(v) {
783            continue;
784        }
785
786        ws.zombies_stack.push((v, false, true));
787
788        // Else, iterate through all children. If any of them is
789        // alive, mark the entire path as alive. Else, just push onto
790        // the stack and continue the DFS.
791        for e in iter_alive_children(txn, channel, v)? {
792            let e = e?;
793            if e.flag().intersects(EdgeFlags::PARENT | EdgeFlags::DELETED) {
794                continue;
795            }
796            let x = txn.find_block(channel, e.dest())?;
797            if is_alive(txn, channel, x)? {
798                // Mark all edges on the path as alive.
799                for (_, alive, on_path) in ws.zombies_stack.iter_mut() {
800                    if *on_path {
801                        *alive = true
802                    }
803                }
804            } else {
805                ws.zombies_stack.push((*x, false, false))
806            }
807        }
808    }
809    ws.zombies_stack.clear();
810    ws.parents.clear();
811    Ok(())
812}
813
814fn collect_zombies_up<T: GraphTxnT>(
815    txn: &T,
816    channel: &T::Graph,
817    to: Position<ChangeId>,
818    ws: &mut Workspace,
819) -> Result<(), BlockError<T::GraphError>> {
820    if let Ok(&to) = txn.find_block(channel, to) {
821        ws.stack.push(to);
822    }
823
824    while let Some(v) = ws.stack.pop() {
825        debug!("remove_zombies, v = {:?}", v);
826        if !ws.parents.insert(v) {
827            continue;
828        }
829        let del_len = ws.del_edges.len();
830        let stack_len = ws.stack.len();
831        for e in iter_adj_all(txn, channel, v)? {
832            let e = e?;
833            debug!("e = {:?}", e);
834            if e.flag().contains(EdgeFlags::PARENT) {
835                assert!(e.flag().contains(EdgeFlags::FOLDER));
836
837                if !e.flag().intersects(EdgeFlags::PSEUDO | EdgeFlags::DELETED) {
838                    // Neither a pseudo edge nor a deleted edge.
839                    ws.del_edges.truncate(del_len);
840                    ws.stack.truncate(stack_len);
841                    break;
842                }
843                if let Ok(x) = txn.find_block_end(channel, e.dest()) {
844                    ws.stack.push(*x)
845                }
846                if e.flag().contains(EdgeFlags::PSEUDO) {
847                    ws.del_edges.push((v, *e))
848                }
849            }
850        }
851    }
852    ws.stack.clear();
853    ws.parents.clear();
854    Ok(())
855}
856
857fn remove_leftover_markings<T: GraphMutTxnT + TreeTxnT>(
858    txn: &mut T,
859    channel: &mut T::Graph,
860    change_id: ChangeId,
861    log: &crate::apply::ZombieRepairLog,
862) -> Result<(), TxnErr<T::GraphError>> {
863    // Drop the leftover DELETED|BLOCK markings still owned by the unrecorded
864    // change (collected for free during `repair_zombies`' DFS — see
865    // [`crate::apply::ZombieRepairLog`]). These are down-context `zombify`
866    // markings the atom-anchored removal in `unapply_edges` can't reach;
867    // leaving them behind would keep a dangling reference to the change we just
868    // unrecorded.
869    for &(v, w) in log.leftover.iter() {
870        del_graph_with_rev(
871            txn,
872            channel,
873            EdgeFlags::DELETED | EdgeFlags::BLOCK,
874            v,
875            w,
876            change_id,
877        )?;
878    }
879    Ok(())
880}
881
882/// Same as crate::apply::collect_missing_contexts, but with the
883/// directions reversed.
884fn collect_missing_contexts<T: GraphMutTxnT + TreeTxnT>(
885    txn: &T,
886    channel: &T::Graph,
887    ws: &mut crate::apply::Workspace,
888    change: &Change,
889    change_id: ChangeId,
890    inodes: &mut HashSet<Position<ChangeId>>,
891) -> Result<(), crate::apply::LocalApplyError<T>> {
892    debug!("collect_missing_contexts");
893    inodes.extend(
894        ws.missing_context
895            .unknown_parents
896            .drain(..)
897            .map(|x| internal_pos(txn, &x.2, change_id).unwrap()),
898    );
899    for atom in change.changes.iter().flat_map(|r| r.iter()) {
900        match atom {
901            // NewVertex is already collected when applying.
902            Atom::NewVertex(_) => {}
903            Atom::EdgeMap(n) => {
904                crate::apply::has_missing_edge_context(
905                    txn, channel, change_id, change, n, inodes, true,
906                )?;
907            }
908        }
909    }
910    Ok(())
911}