1 use crate::apply;
2 use crate::change::*;
3 use crate::changestore::*;
4 use crate::missing_context::*;
5 use crate::pristine::*;
6 
7 use std::collections::{HashMap, HashSet};
8 
9 mod working_copy;
10 
11 #[derive(Debug, Error)]
12 pub enum UnrecordError<
13     ChangestoreError: std::error::Error + 'static,
14     TxnError: std::error::Error + 'static,
15 > {
16     #[error("Changestore error: {0}")]
17     Changestore(ChangestoreError),
18     #[error(transparent)]
19     Txn(TxnError),
20     #[error(transparent)]
21     Block(#[from] crate::pristine::BlockError<TxnError>),
22     #[error(transparent)]
23     InconsistentChange(#[from] crate::pristine::InconsistentChange<TxnError>),
24     #[error("Change not in channel: {}", hash.to_base32())]
25     ChangeNotInChannel { hash: ChangeId },
26     #[error("Change {} is depended upon by {}", change_id.to_base32(), dependent.to_base32())]
27     ChangeIsDependedUpon {
28         change_id: ChangeId,
29         dependent: ChangeId,
30     },
31     #[error(transparent)]
32     Missing(#[from] crate::missing_context::MissingError<TxnError>),
33     #[error(transparent)]
34     LocalApply(#[from] crate::apply::LocalApplyError<TxnError>),
35     #[error(transparent)]
36     Apply(#[from] crate::apply::ApplyError<ChangestoreError, TxnError>),
37 }
38 
39 impl<T: std::error::Error + 'static, C: std::error::Error + 'static> std::convert::From<TxnErr<T>>
40     for UnrecordError<C, T>
41 {
from(t: TxnErr<T>) -> Self42     fn from(t: TxnErr<T>) -> Self {
43         UnrecordError::Txn(t.0)
44     }
45 }
46 
unrecord<T: MutTxnT, P: ChangeStore>( txn: &mut T, channel: &ChannelRef<T>, changes: &P, hash: &Hash, salt: u64, ) -> Result<bool, UnrecordError<P::Error, T::GraphError>>47 pub fn unrecord<T: MutTxnT, P: ChangeStore>(
48     txn: &mut T,
49     channel: &ChannelRef<T>,
50     changes: &P,
51     hash: &Hash,
52     salt: u64,
53 ) -> Result<bool, UnrecordError<P::Error, T::GraphError>> {
54     let change = changes
55         .get_change(hash)
56         .map_err(UnrecordError::Changestore)?;
57     let change_id = if let Some(&h) = txn.get_internal(&hash.into())? {
58         h
59     } else {
60         return Ok(false);
61     };
62     let unused = unused_in_other_channels(txn, &channel, change_id)?;
63     let mut channel = channel.write();
64 
65     del_channel_changes::<T, P>(txn, &mut channel, change_id)?;
66 
67     unapply(txn, &mut channel, changes, change_id, &change, salt)?;
68 
69     if unused {
70         assert!(txn.get_revdep(&change_id, None)?.is_none());
71         while txn.del_dep(&change_id, None)? {}
72         txn.del_external(&change_id, None)?;
73         txn.del_internal(&hash.into(), None)?;
74         for dep in change.dependencies.iter() {
75             let dep = *txn.get_internal(&dep.into())?.unwrap();
76             txn.del_revdep(&dep, Some(&change_id))?;
77         }
78         Ok(false)
79     } else {
80         Ok(true)
81     }
82 }
83 
del_channel_changes< T: ChannelMutTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>, P: ChangeStore, >( txn: &mut T, channel: &mut T::Channel, change_id: ChangeId, ) -> Result<(), UnrecordError<P::Error, T::GraphError>>84 fn del_channel_changes<
85     T: ChannelMutTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>,
86     P: ChangeStore,
87 >(
88     txn: &mut T,
89     channel: &mut T::Channel,
90     change_id: ChangeId,
91 ) -> Result<(), UnrecordError<P::Error, T::GraphError>> {
92     let timestamp = if let Some(&ts) = txn.get_changeset(txn.changes(channel), &change_id)? {
93         ts
94     } else {
95         return Err(UnrecordError::ChangeNotInChannel { hash: change_id });
96     };
97 
98     for x in txn.iter_revdep(&change_id)? {
99         let (p, d) = x?;
100         assert!(*p >= change_id);
101         if *p > change_id {
102             break;
103         }
104         if txn.get_changeset(txn.changes(channel), d)?.is_some() {
105             return Err(UnrecordError::ChangeIsDependedUpon {
106                 change_id,
107                 dependent: *d,
108             });
109         }
110     }
111 
112     txn.del_changes(channel, change_id, timestamp.into())?;
113     Ok(())
114 }
115 
unused_in_other_channels<T: TxnT>( txn: &mut T, channel: &ChannelRef<T>, change_id: ChangeId, ) -> Result<bool, TxnErr<T::GraphError>>116 fn unused_in_other_channels<T: TxnT>(
117     txn: &mut T,
118     channel: &ChannelRef<T>,
119     change_id: ChangeId,
120 ) -> Result<bool, TxnErr<T::GraphError>> {
121     let channel = channel.read();
122     for br in txn.iter_channels("")? {
123         let (name, br) = br?;
124         if name.as_str() == txn.name(&channel) {
125             continue;
126         }
127         let br = br.read();
128         if txn.get_changeset(txn.changes(&br), &change_id)?.is_some() {
129             return Ok(false);
130         }
131     }
132     Ok(true)
133 }
134 
unapply< T: ChannelMutTxnT + TreeMutTxnT<TreeError = <T as GraphTxnT>::GraphError>, C: ChangeStore, >( txn: &mut T, channel: &mut T::Channel, changes: &C, change_id: ChangeId, change: &Change, salt: u64, ) -> Result<(), UnrecordError<C::Error, T::GraphError>>135 fn unapply<
136     T: ChannelMutTxnT + TreeMutTxnT<TreeError = <T as GraphTxnT>::GraphError>,
137     C: ChangeStore,
138 >(
139     txn: &mut T,
140     channel: &mut T::Channel,
141     changes: &C,
142     change_id: ChangeId,
143     change: &Change,
144     salt: u64,
145 ) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
146     let mut clean_inodes = HashSet::new();
147     let mut ws = Workspace::default();
148     for change_ in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
149         match *change_ {
150             Atom::EdgeMap(ref newedges) => unapply_edges(
151                 changes,
152                 txn,
153                 T::graph_mut(channel),
154                 change_id,
155                 newedges,
156                 change,
157                 &mut ws,
158             )?,
159             Atom::NewVertex(ref newvertex) => {
160                 if clean_inodes.insert(newvertex.inode) {
161                     crate::alive::remove_forward_edges(
162                         txn,
163                         T::graph_mut(channel),
164                         internal_pos(txn, &newvertex.inode, change_id)?,
165                     )?
166                 }
167                 unapply_newvertex::<T, C>(
168                     txn,
169                     T::graph_mut(channel),
170                     change_id,
171                     &mut ws,
172                     newvertex,
173                 )?;
174             }
175         }
176     }
177     repair_newvertex_contexts::<T, C>(txn, T::graph_mut(channel), &mut ws, change_id)?;
178     for change in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
179         if let Atom::EdgeMap(ref n) = *change {
180             remove_zombies::<_, C>(txn, T::graph_mut(channel), &mut ws, change_id, n)?;
181             repair_edges_context(
182                 changes,
183                 txn,
184                 T::graph_mut(channel),
185                 &mut ws.apply.missing_context,
186                 change_id,
187                 n,
188             )?
189         }
190     }
191 
192     for change_ in change.changes.iter().rev().flat_map(|r| r.rev_iter()) {
193         match *change_ {
194             Atom::EdgeMap(ref newedges) if newedges.edges.is_empty() => {}
195             Atom::EdgeMap(ref newedges) if newedges.edges[0].flag.contains(EdgeFlags::FOLDER) => {
196                 if newedges.edges[0].flag.contains(EdgeFlags::DELETED) {
197                     working_copy::undo_file_deletion(
198                         txn, changes, channel, change_id, newedges, salt,
199                     )?
200                 } else {
201                     working_copy::undo_file_reinsertion::<C, _>(txn, change_id, newedges)?
202                 }
203             }
204             Atom::NewVertex(ref new_vertex)
205                 if new_vertex.flag.contains(EdgeFlags::FOLDER)
206                     && new_vertex.down_context.is_empty() =>
207             {
208                 working_copy::undo_file_addition(txn, change_id, new_vertex)?;
209             }
210             _ => {}
211         }
212     }
213 
214     crate::apply::clean_obsolete_pseudo_edges(
215         txn,
216         T::graph_mut(channel),
217         &mut ws.apply,
218         change_id,
219     )?;
220     crate::apply::repair_cyclic_paths(txn, T::graph_mut(channel), &mut ws.apply)?;
221     txn.touch_channel(channel, Some(0));
222     Ok(())
223 }
224 
225 #[derive(Default)]
226 struct Workspace {
227     up: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
228     down: HashMap<Vertex<ChangeId>, Position<Option<Hash>>>,
229     parents: HashSet<Vertex<ChangeId>>,
230     del: Vec<SerializedEdge>,
231     apply: crate::apply::Workspace,
232     stack: Vec<Vertex<ChangeId>>,
233     del_edges: Vec<(Vertex<ChangeId>, SerializedEdge)>,
234     must_reintroduce: HashSet<(Vertex<ChangeId>, Vertex<ChangeId>)>,
235 }
236 
unapply_newvertex<T: GraphMutTxnT, C: ChangeStore>( txn: &mut T, channel: &mut T::Graph, change_id: ChangeId, ws: &mut Workspace, new_vertex: &NewVertex<Option<Hash>>, ) -> Result<(), UnrecordError<C::Error, T::GraphError>>237 fn unapply_newvertex<T: GraphMutTxnT, C: ChangeStore>(
238     txn: &mut T,
239     channel: &mut T::Graph,
240     change_id: ChangeId,
241     ws: &mut Workspace,
242     new_vertex: &NewVertex<Option<Hash>>,
243 ) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
244     let mut pos = Position {
245         change: change_id,
246         pos: new_vertex.start,
247     };
248     debug!("unapply_newvertex = {:?}", new_vertex);
249     while let Ok(&vertex) = txn.find_block(channel, pos) {
250         debug!("vertex = {:?}", vertex);
251         for e in iter_adj_all(txn, channel, vertex)? {
252             let e = e?;
253             debug!("e = {:?}", e);
254             if !e.flag().is_deleted() {
255                 if e.flag().is_parent() {
256                     if !e.flag().is_folder() {
257                         let up_v = txn.find_block_end(channel, e.dest())?;
258                         ws.up.insert(*up_v, new_vertex.inode);
259                     }
260                 } else {
261                     let down_v = txn.find_block(channel, e.dest())?;
262                     ws.down.insert(*down_v, new_vertex.inode);
263                     if e.flag().is_folder() {
264                         ws.apply.missing_context.files.insert(*down_v);
265                     }
266                 }
267             }
268             ws.del.push(*e)
269         }
270         debug!("del = {:#?}", ws.del);
271         ws.up.remove(&vertex);
272         ws.down.remove(&vertex);
273         ws.perform_del::<C, T>(txn, channel, vertex)?;
274         if vertex.end < new_vertex.end {
275             pos.pos = vertex.end
276         }
277     }
278     Ok(())
279 }
280 
281 impl Workspace {
perform_del<C: ChangeStore, T: GraphMutTxnT>( &mut self, txn: &mut T, channel: &mut T::Graph, vertex: Vertex<ChangeId>, ) -> Result<(), UnrecordError<C::Error, T::GraphError>>282     fn perform_del<C: ChangeStore, T: GraphMutTxnT>(
283         &mut self,
284         txn: &mut T,
285         channel: &mut T::Graph,
286         vertex: Vertex<ChangeId>,
287     ) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
288         for e in self.del.drain(..) {
289             let (a, b) = if e.flag().is_parent() {
290                 (*txn.find_block_end(channel, e.dest())?, vertex)
291             } else {
292                 (vertex, *txn.find_block(channel, e.dest())?)
293             };
294             del_graph_with_rev(
295                 txn,
296                 channel,
297                 e.flag() - EdgeFlags::PARENT,
298                 a,
299                 b,
300                 e.introduced_by(),
301             )?;
302         }
303         Ok(())
304     }
305 }
306 
repair_newvertex_contexts<T: GraphMutTxnT, C: ChangeStore>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, change_id: ChangeId, ) -> Result<(), UnrecordError<C::Error, T::GraphError>>307 fn repair_newvertex_contexts<T: GraphMutTxnT, C: ChangeStore>(
308     txn: &mut T,
309     channel: &mut T::Graph,
310     ws: &mut Workspace,
311     change_id: ChangeId,
312 ) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
313     debug!("up = {:#?}", ws.up);
314     for (up, inode) in ws.up.drain() {
315         if !is_alive(txn, channel, &up)? {
316             continue;
317         }
318         crate::missing_context::repair_missing_down_context(
319             txn,
320             channel,
321             &mut ws.apply.missing_context,
322             inode,
323             up,
324             &[up],
325         )?
326     }
327 
328     debug!("down = {:#?}", ws.down);
329     for (down, inode) in ws.down.drain() {
330         if !is_alive(txn, channel, &down)? {
331             continue;
332         }
333         for parent in iter_adjacent(
334             txn,
335             channel,
336             down,
337             EdgeFlags::PARENT,
338             EdgeFlags::all() - EdgeFlags::DELETED,
339         )? {
340             let parent = parent?;
341             let parent = txn.find_block_end(channel, parent.dest())?;
342             if !is_alive(txn, channel, parent)? {
343                 ws.parents.insert(*parent);
344             }
345         }
346         debug!("parents {:#?}", ws.parents);
347         for up in ws.parents.drain() {
348             crate::missing_context::repair_missing_up_context(
349                 txn,
350                 channel,
351                 &mut ws.apply.missing_context,
352                 change_id,
353                 inode,
354                 up,
355                 &[down],
356             )?
357         }
358     }
359     Ok(())
360 }
361 
unapply_edges<T: GraphMutTxnT, P: ChangeStore>( changes: &P, txn: &mut T, channel: &mut T::Graph, change_id: ChangeId, newedges: &EdgeMap<Option<Hash>>, change: &Change, ws: &mut Workspace, ) -> Result<(), UnrecordError<P::Error, T::GraphError>>362 fn unapply_edges<T: GraphMutTxnT, P: ChangeStore>(
363     changes: &P,
364     txn: &mut T,
365     channel: &mut T::Graph,
366     change_id: ChangeId,
367     newedges: &EdgeMap<Option<Hash>>,
368     change: &Change,
369     ws: &mut Workspace,
370 ) -> Result<(), UnrecordError<P::Error, T::GraphError>> {
371     debug!("newedges = {:#?}", newedges);
372     let ext: Hash = txn.get_external(&change_id)?.unwrap().into();
373     ws.must_reintroduce.clear();
374     for n in newedges.edges.iter() {
375         let mut source = crate::apply::edge::find_source_vertex(
376             txn,
377             channel,
378             &n.from,
379             change_id,
380             newedges.inode,
381             n.flag,
382             &mut ws.apply,
383         )?;
384         let mut target = crate::apply::edge::find_target_vertex(
385             txn,
386             channel,
387             &n.to,
388             change_id,
389             newedges.inode,
390             n.flag,
391             &mut ws.apply,
392         )?;
393         loop {
394             let intro_ext = n.introduced_by.unwrap_or(ext);
395             let intro = internal(txn, &n.introduced_by, change_id)?.unwrap();
396             if must_reintroduce(
397                 txn, channel, changes, source, target, intro_ext, intro, change_id,
398             )? {
399                 ws.must_reintroduce.insert((source, target));
400             }
401             if target.end >= n.to.end {
402                 break;
403             }
404             source = target;
405             target = *txn
406                 .find_block(channel, target.end_pos())
407                 .map_err(UnrecordError::from)?;
408             assert_ne!(source, target);
409         }
410     }
411     let reintro = std::mem::take(&mut ws.must_reintroduce);
412     for edge in newedges.edges.iter() {
413         let intro = internal(txn, &edge.introduced_by, change_id)?.unwrap();
414         apply::put_newedge(
415             txn,
416             channel,
417             &mut ws.apply,
418             intro,
419             newedges.inode,
420             &edge.reverse(Some(ext)),
421             |a, b| reintro.contains(&(a, b)),
422             |h| change.knows(h),
423         )?;
424     }
425     ws.must_reintroduce = reintro;
426     ws.must_reintroduce.clear();
427     Ok(())
428 }
429 
must_reintroduce<T: GraphTxnT, C: ChangeStore>( txn: &T, channel: &T::Graph, changes: &C, a: Vertex<ChangeId>, b: Vertex<ChangeId>, intro: Hash, intro_id: ChangeId, current_id: ChangeId, ) -> Result<bool, UnrecordError<C::Error, T::GraphError>>430 fn must_reintroduce<T: GraphTxnT, C: ChangeStore>(
431     txn: &T,
432     channel: &T::Graph,
433     changes: &C,
434     a: Vertex<ChangeId>,
435     b: Vertex<ChangeId>,
436     intro: Hash,
437     intro_id: ChangeId,
438     current_id: ChangeId,
439 ) -> Result<bool, UnrecordError<C::Error, T::GraphError>> {
440     debug!("a = {:?}, b = {:?}", a, b);
441     // does a patch introduced by an edge parallel to
442     // this one remove this edge from the graph?
443     let b_ext = Position {
444         change: txn.get_external(&b.change)?.map(From::from),
445         pos: b.start,
446     };
447     let mut stack = Vec::new();
448     for e in iter_adj_all(txn, channel, a)? {
449         let e = e?;
450         if e.flag().contains(EdgeFlags::PARENT)
451             || e.dest() != b.start_pos()
452             || e.introduced_by().is_root()
453             || e.introduced_by() == current_id
454         {
455             continue;
456         }
457         // Optimisation to avoid opening change files in the vast
458         // majority of cases: if there is an edge `e` parallel to a ->
459         // b introduced by the change that introduced a or b, don't
460         // reinsert a -> b: that edge was removed by `e`.
461         if a.change == intro_id || b.change == intro_id {
462             return Ok(false);
463         }
464         stack.push(e.introduced_by())
465     }
466     edge_is_in_channel(txn, changes, b_ext, intro, &mut stack)
467 }
468 
edge_is_in_channel<T: GraphTxnT, C: ChangeStore>( txn: &T, changes: &C, pos: Position<Option<Hash>>, introduced_by: Hash, stack: &mut Vec<ChangeId>, ) -> Result<bool, UnrecordError<C::Error, T::GraphError>>469 fn edge_is_in_channel<T: GraphTxnT, C: ChangeStore>(
470     txn: &T,
471     changes: &C,
472     pos: Position<Option<Hash>>,
473     introduced_by: Hash,
474     stack: &mut Vec<ChangeId>,
475 ) -> Result<bool, UnrecordError<C::Error, T::GraphError>> {
476     let mut visited = HashSet::new();
477     while let Some(s) = stack.pop() {
478         if !visited.insert(s) {
479             continue;
480         }
481         debug!("stack: {:?}", s);
482         for next in changes
483             .change_deletes_position(|c| txn.get_external(&c).unwrap().map(From::from), s, pos)
484             .map_err(UnrecordError::Changestore)?
485         {
486             if next == introduced_by {
487                 return Ok(false);
488             } else if let Some(i) = txn.get_internal(&next.into())? {
489                 stack.push(*i)
490             }
491         }
492     }
493     Ok(true)
494 }
495 
remove_zombies<T: GraphMutTxnT, C: ChangeStore>( txn: &mut T, channel: &mut T::Graph, ws: &mut Workspace, change_id: ChangeId, newedges: &EdgeMap<Option<Hash>>, ) -> Result<(), UnrecordError<C::Error, T::GraphError>>496 fn remove_zombies<T: GraphMutTxnT, C: ChangeStore>(
497     txn: &mut T,
498     channel: &mut T::Graph,
499     ws: &mut Workspace,
500     change_id: ChangeId,
501     newedges: &EdgeMap<Option<Hash>>,
502 ) -> Result<(), UnrecordError<C::Error, T::GraphError>> {
503     debug!("remove_zombies, change_id = {:?}", change_id);
504     for edge in newedges.edges.iter() {
505         let to = internal_pos(txn, &edge.to.start_pos(), change_id)?;
506         collect_zombies(txn, channel, change_id, to, ws)?;
507         debug!("remove_zombies = {:#?}", ws.del_edges);
508         for (v, mut e) in ws.del_edges.drain(..) {
509             if e.flag().contains(EdgeFlags::PARENT) {
510                 let u = *txn.find_block_end(channel, e.dest())?;
511                 e -= EdgeFlags::PARENT;
512                 del_graph_with_rev(txn, channel, e.flag(), u, v, e.introduced_by())?;
513             } else {
514                 let w = *txn.find_block(channel, e.dest())?;
515                 del_graph_with_rev(txn, channel, e.flag(), v, w, e.introduced_by())?;
516             }
517         }
518     }
519     Ok(())
520 }
521 
collect_zombies<T: GraphTxnT>( txn: &T, channel: &T::Graph, change_id: ChangeId, to: Position<ChangeId>, ws: &mut Workspace, ) -> Result<(), BlockError<T::GraphError>>522 fn collect_zombies<T: GraphTxnT>(
523     txn: &T,
524     channel: &T::Graph,
525     change_id: ChangeId,
526     to: Position<ChangeId>,
527     ws: &mut Workspace,
528 ) -> Result<(), BlockError<T::GraphError>> {
529     ws.stack.push(*txn.find_block(channel, to)?);
530     while let Some(v) = ws.stack.pop() {
531         debug!("remove_zombies, v = {:?}", v);
532         if !ws.parents.insert(v) {
533             continue;
534         }
535         for e in iter_adj_all(txn, channel, v)? {
536             let e = e?;
537             debug!("e = {:?}", e);
538             if !(e.introduced_by() == change_id || e.flag() & EdgeFlags::bp() == EdgeFlags::PARENT)
539             {
540                 continue;
541             }
542             if e.flag().contains(EdgeFlags::PARENT) {
543                 ws.stack.push(*txn.find_block_end(channel, e.dest())?)
544             } else {
545                 ws.stack.push(*txn.find_block(channel, e.dest())?)
546             }
547             if e.introduced_by() == change_id {
548                 ws.del_edges.push((v, *e))
549             }
550         }
551     }
552     ws.stack.clear();
553     ws.parents.clear();
554     Ok(())
555 }
556 
repair_edges_context<T: GraphMutTxnT, P: ChangeStore>( changes: &P, txn: &mut T, channel: &mut T::Graph, ws: &mut crate::missing_context::Workspace, change_id: ChangeId, n: &EdgeMap<Option<Hash>>, ) -> Result<(), UnrecordError<P::Error, T::GraphError>>557 fn repair_edges_context<T: GraphMutTxnT, P: ChangeStore>(
558     changes: &P,
559     txn: &mut T,
560     channel: &mut T::Graph,
561     ws: &mut crate::missing_context::Workspace,
562     change_id: ChangeId,
563     n: &EdgeMap<Option<Hash>>,
564 ) -> Result<(), UnrecordError<P::Error, T::GraphError>> {
565     debug!("repair_edges_context");
566     let change_hash: Hash = txn.get_external(&change_id)?.unwrap().into();
567     for e in n.edges.iter() {
568         assert!(!e.flag.contains(EdgeFlags::PARENT));
569         let intro = internal(txn, &e.introduced_by, change_id)?.unwrap();
570         if e.previous.contains(EdgeFlags::DELETED) {
571             repair_context_deleted(
572                 txn,
573                 channel,
574                 ws,
575                 n.inode,
576                 intro,
577                 |h| changes.knows(&change_hash, &h).unwrap(),
578                 &e.reverse(Some(change_hash)),
579             )?
580         } else {
581             let to = internal_pos(txn, &e.to.start_pos(), change_id)?;
582             let to = txn.find_block(channel, to)?;
583             debug!("to = {:?}", to);
584             if !is_alive(txn, channel, to)? {
585                 debug!("not alive");
586                 continue;
587             }
588             repair_context_nondeleted(
589                 txn,
590                 channel,
591                 ws,
592                 n.inode,
593                 intro,
594                 &e.reverse(Some(change_hash)),
595             )?
596         }
597     }
598     Ok(())
599 }
600