1 use crate::alive::{Graph, VertexId};
2 use crate::change::*;
3 use crate::find_alive::*;
4 use crate::pristine::*;
5 use crate::{HashMap, HashSet};
6 use std::collections::hash_map::Entry;
7
8 #[derive(Debug, Error)]
9 pub enum MissingError<TxnError: std::error::Error + 'static> {
10 #[error(transparent)]
11 Txn(TxnError),
12 #[error(transparent)]
13 Block(#[from] BlockError<TxnError>),
14 #[error(transparent)]
15 Inconsistent(#[from] InconsistentChange<TxnError>),
16 }
17
18 impl<T: std::error::Error + 'static> std::convert::From<TxnErr<T>> for MissingError<T> {
from(e: TxnErr<T>) -> Self19 fn from(e: TxnErr<T>) -> Self {
20 MissingError::Txn(e.0)
21 }
22 }
23
24 impl Workspace {
load_graph<T: GraphTxnT>( &mut self, txn: &T, channel: &T::Graph, inode: Position<Option<Hash>>, ) -> Result< Option<&(Graph, HashMap<Vertex<ChangeId>, VertexId>)>, InconsistentChange<T::GraphError>, >25 pub(crate) fn load_graph<T: GraphTxnT>(
26 &mut self,
27 txn: &T,
28 channel: &T::Graph,
29 inode: Position<Option<Hash>>,
30 ) -> Result<
31 Option<&(Graph, HashMap<Vertex<ChangeId>, VertexId>)>,
32 InconsistentChange<T::GraphError>,
33 > {
34 if let Some(change) = inode.change {
35 match self.graphs.0.entry(inode) {
36 Entry::Occupied(e) => Ok(Some(e.into_mut())),
37 Entry::Vacant(v) => {
38 let pos = Position {
39 change: if let Some(&i) = txn.get_internal(&change.into())? {
40 i
41 } else {
42 return Err(InconsistentChange::UndeclaredDep);
43 },
44 pos: inode.pos,
45 };
46 let mut graph = crate::alive::retrieve(txn, channel, pos)?;
47 graph.tarjan();
48 let mut ids = HashMap::default();
49 for (i, l) in graph.lines.iter().enumerate() {
50 ids.insert(l.vertex, VertexId(i));
51 }
52 Ok(Some(v.insert((graph, ids))))
53 }
54 }
55 } else {
56 Ok(None)
57 }
58 }
59 }
60
repair_missing_up_context< 'a, T: GraphMutTxnT, I: IntoIterator<Item = &'a Vertex<ChangeId>>, >( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, change_id: ChangeId, inode: Position<Option<Hash>>, c: Vertex<ChangeId>, d: I, ) -> Result<(), MissingError<T::GraphError>>61 pub(crate) fn repair_missing_up_context<
62 'a,
63 T: GraphMutTxnT,
64 I: IntoIterator<Item = &'a Vertex<ChangeId>>,
65 >(
66 txn: &mut T,
67 channel: &mut T::Graph,
68 ws: &mut Workspace,
69 change_id: ChangeId,
70 inode: Position<Option<Hash>>,
71 c: Vertex<ChangeId>,
72 d: I,
73 ) -> Result<(), MissingError<T::GraphError>> {
74 let now = std::time::Instant::now();
75 let mut alive = find_alive_up(txn, channel, &mut ws.files, c, change_id)?;
76 crate::TIMERS.lock().unwrap().find_alive += now.elapsed();
77 ws.load_graph(txn, channel, inode)?;
78
79 debug!("repair_missing_up_context, alive = {:?}", alive);
80 for &d in d {
81 if let Some((graph, vids)) = ws.graphs.0.get(&inode) {
82 crate::alive::remove_redundant_parents(
83 graph,
84 vids,
85 &mut alive,
86 &mut ws.covered_parents,
87 d,
88 );
89 }
90 repair_regular_up(txn, channel, &alive, d, EdgeFlags::PSEUDO)?;
91 }
92 Ok(())
93 }
94
repair_regular_up<T: GraphMutTxnT>( txn: &mut T, channel: &mut T::Graph, alive: &HashSet<Vertex<ChangeId>>, d: Vertex<ChangeId>, flag: EdgeFlags, ) -> Result<(), TxnErr<T::GraphError>>95 fn repair_regular_up<T: GraphMutTxnT>(
96 txn: &mut T,
97 channel: &mut T::Graph,
98 alive: &HashSet<Vertex<ChangeId>>,
99 d: Vertex<ChangeId>,
100 flag: EdgeFlags,
101 ) -> Result<(), TxnErr<T::GraphError>> {
102 for &ancestor in alive.iter() {
103 debug!("put_graph_with_rev {:?} -> {:?}", ancestor, d);
104 if ancestor == d {
105 info!(
106 "repair_missing_up_context, alive: {:?} == {:?}",
107 ancestor, d
108 );
109 continue;
110 }
111 debug!("repair_missing_up {:?} {:?}", ancestor, d);
112 put_graph_with_rev(txn, channel, flag, ancestor, d, ChangeId::ROOT)?;
113 }
114 Ok(())
115 }
116
repair_missing_down_context< 'a, T: GraphMutTxnT, I: IntoIterator<Item = &'a Vertex<ChangeId>>, >( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, inode: Position<Option<Hash>>, c: Vertex<ChangeId>, d: I, ) -> Result<(), MissingError<T::GraphError>>117 pub(crate) fn repair_missing_down_context<
118 'a,
119 T: GraphMutTxnT,
120 I: IntoIterator<Item = &'a Vertex<ChangeId>>,
121 >(
122 txn: &mut T,
123 channel: &mut T::Graph,
124 ws: &mut Workspace,
125 inode: Position<Option<Hash>>,
126 c: Vertex<ChangeId>,
127 d: I,
128 ) -> Result<(), MissingError<T::GraphError>> {
129 let now = std::time::Instant::now();
130 let mut alive = find_alive_down(txn, channel, c)?;
131 crate::TIMERS.lock().unwrap().find_alive += now.elapsed();
132 ws.load_graph(txn, channel, inode)?;
133 if let Some((graph, vids)) = ws.graphs.0.get(&inode) {
134 crate::alive::remove_redundant_children(graph, vids, &mut alive, c);
135 }
136
137 if !alive.is_empty() {
138 debug!("repair_missing_down_context alive = {:#?}", alive);
139 }
140
141 for &d in d {
142 for &desc in alive.iter() {
143 if d == desc {
144 info!("repair_missing_down_context, alive: {:?} == {:?}", d, desc);
145 continue;
146 }
147 debug!("repair_missing_down {:?} {:?}", d, desc);
148 put_graph_with_rev(txn, channel, EdgeFlags::PSEUDO, d, desc, ChangeId::ROOT)?;
149 }
150 }
151 Ok(())
152 }
153
repair_context_nondeleted<T: GraphMutTxnT>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, inode: Position<Option<Hash>>, change_id: ChangeId, e: &NewEdge<Option<Hash>>, ) -> Result<(), MissingError<T::GraphError>>154 pub(crate) fn repair_context_nondeleted<T: GraphMutTxnT>(
155 txn: &mut T,
156 channel: &mut T::Graph,
157 ws: &mut Workspace,
158 inode: Position<Option<Hash>>,
159 change_id: ChangeId,
160 e: &NewEdge<Option<Hash>>,
161 ) -> Result<(), MissingError<T::GraphError>> {
162 if e.flag.contains(EdgeFlags::FOLDER) {
163 return Ok(());
164 }
165 let source = *txn.find_block_end(&channel, internal_pos(txn, &e.from, change_id)?)?;
166 let target = *txn.find_block(&channel, internal_pos(txn, &e.to.start_pos(), change_id)?)?;
167 repair_missing_up_context(txn, channel, ws, change_id, inode, source, &[target])?;
168 reconnect_target_up(txn, channel, ws, inode, target, change_id)?;
169 if e.flag.contains(EdgeFlags::BLOCK) {
170 repair_missing_down_context(txn, channel, ws, inode, target, &[target])?;
171 } else if is_alive(txn, channel, &source)? {
172 repair_missing_down_context(txn, channel, ws, inode, target, &[source])?;
173 }
174 Ok(())
175 }
176
reconnect_target_up<T: GraphMutTxnT>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, inode: Position<Option<Hash>>, target: Vertex<ChangeId>, change_id: ChangeId, ) -> Result<(), MissingError<T::GraphError>>177 fn reconnect_target_up<T: GraphMutTxnT>(
178 txn: &mut T,
179 channel: &mut T::Graph,
180 ws: &mut Workspace,
181 inode: Position<Option<Hash>>,
182 target: Vertex<ChangeId>,
183 change_id: ChangeId,
184 ) -> Result<(), MissingError<T::GraphError>> {
185 let mut unknown = HashSet::default();
186 for v in iter_deleted_parents(txn, channel, target)? {
187 let v = v?;
188 if v.dest().change.is_root() || v.introduced_by().is_root() {
189 continue;
190 }
191 if v.introduced_by() == change_id {
192 unknown.clear();
193 break;
194 }
195 // Else change ~v.introduced_by~ is a change we don't know,
196 // since no change can create a conflict with itself.
197 unknown.insert(*txn.find_block_end(channel, v.dest())?);
198 }
199 for up in unknown.drain() {
200 repair_missing_up_context(txn, channel, ws, change_id, inode, up, &[target])?;
201 }
202 Ok(())
203 }
204
repair_context_deleted<T: GraphMutTxnT, K>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, inode: Position<Option<Hash>>, change_id: ChangeId, mut known: K, e: &NewEdge<Option<Hash>>, ) -> Result<(), MissingError<T::GraphError>> where K: FnMut(Hash) -> bool,205 pub(crate) fn repair_context_deleted<T: GraphMutTxnT, K>(
206 txn: &mut T,
207 channel: &mut T::Graph,
208 ws: &mut Workspace,
209 inode: Position<Option<Hash>>,
210 change_id: ChangeId,
211 mut known: K,
212 e: &NewEdge<Option<Hash>>,
213 ) -> Result<(), MissingError<T::GraphError>>
214 where
215 K: FnMut(Hash) -> bool,
216 {
217 if e.flag.contains(EdgeFlags::FOLDER) {
218 return Ok(());
219 }
220 debug!("repair_context_deleted {:?}", e);
221 let mut pos = internal_pos(txn, &e.to.start_pos(), change_id)?;
222 while let Ok(&dest_vertex) = txn.find_block(&channel, pos) {
223 debug!("repair_context_deleted, dest_vertex = {:?}", dest_vertex);
224 repair_children_of_deleted(txn, channel, ws, inode, &mut known, change_id, dest_vertex)?;
225 if dest_vertex.end < e.to.end {
226 pos.pos = dest_vertex.end
227 } else {
228 break;
229 }
230 }
231 Ok(())
232 }
233
234 /// If we're deleting a folder edge, there is a possibility that this
235 /// solves a "zombie conflict", by removing the last child of a folder
236 /// that was a zombie, in which case that parent folder can finally
237 /// rest in peace.
238 ///
239 /// This function takes care of this for the entire change, by
240 /// removing all obsolete folder conflict edges.
detect_folder_conflict_resolutions<T: GraphMutTxnT>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, change_id: ChangeId, change: &Change, ) -> Result<(), MissingError<T::GraphError>>241 pub(crate) fn detect_folder_conflict_resolutions<T: GraphMutTxnT>(
242 txn: &mut T,
243 channel: &mut T::Graph,
244 ws: &mut Workspace,
245 change_id: ChangeId,
246 change: &Change,
247 ) -> Result<(), MissingError<T::GraphError>> {
248 for change_ in change.changes.iter() {
249 for change_ in change_.iter() {
250 if let Atom::EdgeMap(ref n) = *change_ {
251 for edge in n.edges.iter() {
252 if !edge.flag.contains(EdgeFlags::DELETED) {
253 continue;
254 }
255 detect_folder_conflict_resolution(txn, channel, ws, change_id, &n.inode, edge)?
256 }
257 }
258 }
259 }
260 Ok(())
261 }
262
detect_folder_conflict_resolution<T: GraphMutTxnT>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, change_id: ChangeId, inode: &Position<Option<Hash>>, e: &NewEdge<Option<Hash>>, ) -> Result<(), MissingError<T::GraphError>>263 fn detect_folder_conflict_resolution<T: GraphMutTxnT>(
264 txn: &mut T,
265 channel: &mut T::Graph,
266 ws: &mut Workspace,
267 change_id: ChangeId,
268 inode: &Position<Option<Hash>>,
269 e: &NewEdge<Option<Hash>>,
270 ) -> Result<(), MissingError<T::GraphError>> {
271 let mut stack = vec![if e.flag.contains(EdgeFlags::FOLDER) {
272 if e.to.is_empty() {
273 internal_pos(txn, &e.to.start_pos(), change_id)?
274 } else {
275 internal_pos(txn, &e.from, change_id)?
276 }
277 } else {
278 internal_pos(txn, &inode, change_id)?
279 }];
280 let len = ws.pseudo.len();
281 while let Some(pos) = stack.pop() {
282 let dest_vertex = if let Ok(&dest_vertex) = txn.find_block_end(&channel, pos) {
283 if !dest_vertex.is_empty() {
284 continue;
285 }
286 dest_vertex
287 } else {
288 continue;
289 };
290 // Is `dest_vertex` alive? If so, stop this path.
291 let f0 = EdgeFlags::FOLDER | EdgeFlags::PARENT;
292 let f1 = EdgeFlags::FOLDER | EdgeFlags::PARENT | EdgeFlags::BLOCK;
293 if let Some(e) = iter_adjacent(txn, channel, dest_vertex, f0, f1)?
294 .filter_map(|e| e.ok())
295 .filter(|e| !e.flag().contains(EdgeFlags::PSEUDO))
296 .next()
297 {
298 debug!("is_alive: {:?}", e);
299 continue;
300 }
301 // Does `dest_vertex` have alive or zombie descendants? If
302 // so, stop this path.
303 let f0 = EdgeFlags::empty();
304 let f1 = EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::PSEUDO;
305 if let Some(e) = iter_adjacent(txn, channel, dest_vertex, f0, f1)?.next() {
306 debug!("child is_alive: {:?}", e);
307 continue;
308 }
309 // Else, `dest_vertex` is dead. We should remove its
310 // pseudo-parents.
311 let f = EdgeFlags::FOLDER | EdgeFlags::PARENT | EdgeFlags::PSEUDO;
312 for e in iter_adjacent(txn, channel, dest_vertex, f, f)? {
313 let e = e?;
314 ws.pseudo.push((dest_vertex, *e));
315 let gr = *txn.find_block_end(&channel, e.dest()).unwrap();
316 for e in iter_adjacent(txn, channel, gr, f, f)? {
317 let e = e?;
318 ws.pseudo.push((gr, *e));
319 stack.push(e.dest())
320 }
321 }
322 }
323 // Finally, we were only squatting the `.pseudo` field of the
324 // workspace, since that field is normally used for missing
325 // children, and pseudo-edges from that field are treated in a
326 // special way (by `delete_pseudo_edges` in this module).
327 //
328 // Therefore, we need to delete our folder-pseudo-edges now.
329 for (v, e) in ws.pseudo.drain(len..) {
330 let p = *txn.find_block_end(channel, e.dest())?;
331 del_graph_with_rev(
332 txn,
333 channel,
334 e.flag() - EdgeFlags::PARENT,
335 p,
336 v,
337 e.introduced_by(),
338 )?;
339 }
340 Ok(())
341 }
342
343 #[derive(Default)]
344 pub struct Workspace {
345 pub(crate) unknown_parents: Vec<(
346 Vertex<ChangeId>,
347 Vertex<ChangeId>,
348 Position<Option<Hash>>,
349 EdgeFlags,
350 )>,
351 unknown: Vec<SerializedEdge>,
352 pub(crate) parents: HashSet<SerializedEdge>,
353 pub(crate) pseudo: Vec<(Vertex<ChangeId>, SerializedEdge)>,
354 repaired: HashSet<Vertex<ChangeId>>,
355 pub(crate) graphs: Graphs,
356 pub(crate) covered_parents: HashSet<(Vertex<ChangeId>, Vertex<ChangeId>)>,
357 pub(crate) files: HashSet<Vertex<ChangeId>>,
358 }
359
360 #[derive(Debug, Default)]
361 pub(crate) struct Graphs(
362 pub HashMap<Position<Option<Hash>>, (Graph, HashMap<Vertex<ChangeId>, crate::alive::VertexId>)>,
363 );
364
365 impl Graphs {
get( &self, inode: Position<Option<Hash>>, ) -> Option<&(Graph, HashMap<Vertex<ChangeId>, VertexId>)>366 pub(crate) fn get(
367 &self,
368 inode: Position<Option<Hash>>,
369 ) -> Option<&(Graph, HashMap<Vertex<ChangeId>, VertexId>)> {
370 self.0.get(&inode)
371 }
372
split( &mut self, inode: Position<Option<Hash>>, vertex: Vertex<ChangeId>, mid: ChangePosition, )373 pub fn split(
374 &mut self,
375 inode: Position<Option<Hash>>,
376 vertex: Vertex<ChangeId>,
377 mid: ChangePosition,
378 ) {
379 if let Some((_, vids)) = self.0.get_mut(&inode) {
380 if let Some(vid) = vids.remove(&vertex) {
381 vids.insert(Vertex { end: mid, ..vertex }, vid);
382 vids.insert(
383 Vertex {
384 start: mid,
385 ..vertex
386 },
387 vid,
388 );
389 }
390 }
391 }
392 }
393
394 impl Workspace {
clear(&mut self)395 pub fn clear(&mut self) {
396 self.unknown.clear();
397 self.unknown_parents.clear();
398 self.pseudo.clear();
399 self.parents.clear();
400 self.graphs.0.clear();
401 self.repaired.clear();
402 self.covered_parents.clear();
403 }
assert_empty(&self)404 pub fn assert_empty(&self) {
405 assert!(self.unknown.is_empty());
406 assert!(self.unknown_parents.is_empty());
407 assert!(self.pseudo.is_empty());
408 assert!(self.parents.is_empty());
409 assert!(self.graphs.0.is_empty());
410 assert!(self.repaired.is_empty());
411 assert!(self.covered_parents.is_empty());
412 }
413 }
414
collect_unknown_children<T: GraphTxnT, K>( txn: &T, channel: &T::Graph, ws: &mut Workspace, dest_vertex: Vertex<ChangeId>, change_id: ChangeId, known: &mut K, ) -> Result<(), TxnErr<T::GraphError>> where K: FnMut(Hash) -> bool,415 fn collect_unknown_children<T: GraphTxnT, K>(
416 txn: &T,
417 channel: &T::Graph,
418 ws: &mut Workspace,
419 dest_vertex: Vertex<ChangeId>,
420 change_id: ChangeId,
421 known: &mut K,
422 ) -> Result<(), TxnErr<T::GraphError>>
423 where
424 K: FnMut(Hash) -> bool,
425 {
426 for v in iter_alive_children(txn, channel, dest_vertex)? {
427 let v = v?;
428 debug!(
429 "collect_unknown_children dest_vertex = {:?}, v = {:?}",
430 dest_vertex, v
431 );
432 if v.introduced_by() == change_id || v.dest().change.is_root() {
433 continue;
434 }
435 if v.introduced_by().is_root() {
436 ws.pseudo.push((dest_vertex, *v));
437 continue;
438 }
439 let mut not_del_by_change = true;
440 for e in iter_adjacent(
441 txn,
442 channel,
443 dest_vertex,
444 EdgeFlags::PARENT | EdgeFlags::DELETED,
445 EdgeFlags::all(),
446 )? {
447 let e = e?;
448 if e.introduced_by() == v.introduced_by() {
449 not_del_by_change = false;
450 break;
451 }
452 }
453 if not_del_by_change {
454 let intro = txn.get_external(&v.introduced_by())?.unwrap().into();
455 if !known(intro) {
456 ws.unknown.push(*v);
457 }
458 }
459 }
460 Ok(())
461 }
462
repair_children_of_deleted<T: GraphMutTxnT, K>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, inode: Position<Option<Hash>>, mut known: K, change_id: ChangeId, dest_vertex: Vertex<ChangeId>, ) -> Result<(), MissingError<T::GraphError>> where K: FnMut(Hash) -> bool,463 fn repair_children_of_deleted<T: GraphMutTxnT, K>(
464 txn: &mut T,
465 channel: &mut T::Graph,
466 ws: &mut Workspace,
467 inode: Position<Option<Hash>>,
468 mut known: K,
469 change_id: ChangeId,
470 dest_vertex: Vertex<ChangeId>,
471 ) -> Result<(), MissingError<T::GraphError>>
472 where
473 K: FnMut(Hash) -> bool,
474 {
475 trace!("repair_children_of_deleted {:?}", dest_vertex);
476 collect_unknown_children(txn, channel, ws, dest_vertex, change_id, &mut known)?;
477 let mut unknown = std::mem::replace(&mut ws.unknown, Vec::new());
478 debug!("dest_vertex = {:?}, unknown = {:?}", dest_vertex, unknown);
479 for edge in unknown.drain(..) {
480 let p = *txn.find_block(channel, edge.dest())?;
481 assert!(!edge.flag().contains(EdgeFlags::FOLDER));
482 debug!("dest_vertex {:?}, p {:?}", dest_vertex, p);
483 put_graph_with_rev(txn, channel, EdgeFlags::db(), dest_vertex, p, change_id)?;
484 let mut u = p;
485 while let Ok(&v) = txn.find_block(channel, u.end_pos()) {
486 if u != v {
487 debug!("repair_children_of_deleted: {:?} -> {:?}", u, v);
488 put_graph_with_rev(txn, channel, EdgeFlags::db(), u, v, change_id)?;
489 u = v
490 } else {
491 break;
492 }
493 }
494 if is_alive(txn, channel, &p)? {
495 repair_missing_up_context(txn, channel, ws, change_id, inode, dest_vertex, &[p])?;
496 } else {
497 let alive = find_alive_down(txn, channel, p)?;
498 repair_missing_up_context(txn, channel, ws, change_id, inode, dest_vertex, &alive)?;
499 }
500 }
501 ws.unknown = unknown;
502 Ok(())
503 }
504
delete_pseudo_edges<T: GraphMutTxnT>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, ) -> Result<(), MissingError<T::GraphError>>505 pub(crate) fn delete_pseudo_edges<T: GraphMutTxnT>(
506 txn: &mut T,
507 channel: &mut T::Graph,
508 ws: &mut Workspace,
509 ) -> Result<(), MissingError<T::GraphError>> {
510 if ws.pseudo.is_empty() {
511 debug!("no pseudo edges")
512 }
513 for (dest_vertex, mut e) in ws.pseudo.drain(..) {
514 debug!("repair_context_deleted, deleting {:?} {:?}", dest_vertex, e);
515 if !is_alive(txn, channel, &dest_vertex)? && !ws.repaired.contains(&dest_vertex) {
516 if e.flag().contains(EdgeFlags::PARENT) {
517 let p = *txn.find_block_end(channel, e.dest())?;
518 if !is_alive(txn, channel, &p)? {
519 debug!("delete {:?} {:?}", p, dest_vertex);
520 e -= EdgeFlags::PARENT;
521 del_graph_with_rev(txn, channel, e.flag(), p, dest_vertex, e.introduced_by())?;
522 }
523 } else {
524 let p = *txn.find_block(channel, e.dest())?;
525 if !is_alive(txn, channel, &p)? {
526 debug!("delete (2) {:?} {:?}", dest_vertex, p);
527 del_graph_with_rev(txn, channel, e.flag(), dest_vertex, p, e.introduced_by())?;
528 }
529 }
530 }
531 }
532 Ok(())
533 }
534
repair_parents_of_deleted<T: GraphMutTxnT>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, ) -> Result<(), MissingError<T::GraphError>>535 pub(crate) fn repair_parents_of_deleted<T: GraphMutTxnT>(
536 txn: &mut T,
537 channel: &mut T::Graph,
538 ws: &mut Workspace,
539 ) -> Result<(), MissingError<T::GraphError>> {
540 debug!("repair_parents_of_deleted");
541 let mut unknown = std::mem::replace(&mut ws.unknown_parents, Vec::new());
542 for (dest_vertex, p, inode, flag) in unknown.drain(..) {
543 if flag.contains(EdgeFlags::FOLDER) {
544 repair_missing_down_context(txn, channel, ws, inode, dest_vertex, &[dest_vertex])?
545 } else {
546 repair_missing_down_context(txn, channel, ws, inode, dest_vertex, &[p])?
547 }
548 }
549 ws.unknown_parents = unknown;
550 Ok(())
551 }
552