Skip to main content

pijul_core/
record.rs

1//! Hunk a change from a pristine and a working copy.
2use crate::changestore::ChangeStore;
3use crate::diff;
4pub use crate::diff::Algorithm;
5use crate::path::{Components, components};
6use crate::pristine::*;
7use crate::small_string::SmallString;
8use crate::working_copy::WorkingCopyRead;
9use crate::{HashMap, HashSet};
10use crate::{alive::retrieve, text_encoding::Encoding};
11use crate::{change::*, changestore::FileMetadata};
12use parking_lot::Mutex;
13use std::collections::VecDeque;
14use std::sync::Arc;
15
16#[derive(Error)]
17pub enum RecordError<C: std::error::Error + 'static, W: std::error::Error, T: GraphTxnT + TreeTxnT>
18{
19    #[error("Changestore error: {0}")]
20    Changestore(C),
21    #[error("Working copy error: {0}")]
22    WorkingCopy(W),
23    #[error("System time error: {0}")]
24    SystemTimeError(#[from] std::time::SystemTimeError),
25    #[error(transparent)]
26    Txn(#[from] TxnErr<T::GraphError>),
27    #[error(transparent)]
28    Tree(#[from] TreeErr<T::TreeError>),
29    #[error(transparent)]
30    Diff(#[from] diff::DiffError<C, T>),
31    #[error("Path not in repository: {0}")]
32    PathNotInRepo(String),
33    #[error(transparent)]
34    Io(#[from] std::io::Error),
35}
36
37impl<C: std::error::Error, W: std::error::Error, T: GraphTxnT + TreeTxnT> std::fmt::Debug
38    for RecordError<C, W, T>
39{
40    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
41        match self {
42            RecordError::Changestore(e) => std::fmt::Debug::fmt(e, fmt),
43            RecordError::WorkingCopy(e) => std::fmt::Debug::fmt(e, fmt),
44            RecordError::SystemTimeError(e) => std::fmt::Debug::fmt(e, fmt),
45            RecordError::Txn(e) => std::fmt::Debug::fmt(e, fmt),
46            RecordError::Tree(e) => std::fmt::Debug::fmt(e, fmt),
47            RecordError::Diff(e) => std::fmt::Debug::fmt(e, fmt),
48            RecordError::PathNotInRepo(p) => write!(fmt, "Path not in repository: {}", p),
49            RecordError::Io(e) => std::fmt::Debug::fmt(e, fmt),
50        }
51    }
52}
53
54impl<C: std::error::Error + 'static, W: std::error::Error + 'static, T: GraphTxnT + TreeTxnT>
55    std::convert::From<crate::output::FileError<C, T>> for RecordError<C, W, T>
56{
57    fn from(e: crate::output::FileError<C, T>) -> Self {
58        match e {
59            crate::output::FileError::Changestore(e) => RecordError::Changestore(e),
60            crate::output::FileError::Io(e) => RecordError::Io(e),
61            crate::output::FileError::Txn(t) => RecordError::Txn(t),
62        }
63    }
64}
65
66/// A change in the process of being recorded. This is typically
67/// created using `Builder::new`.
68pub struct Builder {
69    pub(crate) rec: Vec<Arc<Mutex<Recorded>>>,
70    recorded_inodes: Arc<Mutex<HashMap<Inode, Position<Option<ChangeId>>>>>,
71    deleted_vertices: Arc<Mutex<HashSet<Position<ChangeId>>>>,
72    pub force_rediff: bool,
73    pub ignore_missing: bool,
74    pub contents: Arc<Mutex<Vec<u8>>>,
75    new_root: Arc<Mutex<Option<NewRoot>>>,
76}
77
78type NewRoot = (Position<Option<ChangeId>>, u64);
79
80#[derive(Debug)]
81struct Parent {
82    basename: String,
83    metadata: InodeMetadata,
84    encoding: Option<Encoding>,
85    parent: Position<Option<ChangeId>>,
86}
87
88/// The result of recording a change:
89pub struct Recorded {
90    /// The "byte contents" of the change.
91    pub contents: Arc<Mutex<Vec<u8>>>,
92    /// The current records, to be lated converted into change operations.
93    pub actions: Vec<Hunk<Option<ChangeId>, LocalByte>>,
94    /// The updates that need to be made to the ~tree~ and ~revtree~
95    /// tables when this change is applied to the local repository.
96    pub updatables: HashMap<usize, InodeUpdate>,
97    /// The size of the largest file that was recorded in this change.
98    pub largest_file: u64,
99    /// Whether we have recorded binary files.
100    pub has_binary_files: bool,
101    /// Timestamp of the oldest changed file. If nothing changed,
102    /// returns now().
103    pub oldest_change: std::time::SystemTime,
104    /// Redundant edges found during the comparison.
105    pub redundant: Vec<crate::alive::Redundant>,
106    /// Force a re-diff
107    force_rediff: bool,
108    deleted_vertices: Arc<Mutex<HashSet<Position<ChangeId>>>>,
109    recorded_inodes: Arc<Mutex<HashMap<Inode, Position<Option<ChangeId>>>>>,
110    new_root: Arc<Mutex<Option<NewRoot>>>,
111}
112
113impl Default for Builder {
114    fn default() -> Self {
115        Self {
116            rec: Vec::new(),
117            recorded_inodes: Arc::new(Mutex::new(HashMap::default())),
118            force_rediff: false,
119            ignore_missing: false,
120            deleted_vertices: Arc::new(Mutex::new(HashSet::default())),
121            contents: Arc::new(Mutex::new(Vec::new())),
122            new_root: Arc::new(Mutex::new(None)),
123        }
124    }
125}
126
127impl Builder {
128    /// Initialise a `Builder`.
129    pub fn new() -> Self {
130        Self::default()
131    }
132
133    pub fn recorded(&mut self) -> Arc<Mutex<Recorded>> {
134        let m = Arc::new(Mutex::new(self.recorded_()));
135        self.rec.push(m.clone());
136        m
137    }
138
139    fn recorded_(&self) -> Recorded {
140        Recorded {
141            contents: self.contents.clone(),
142            actions: Vec::new(),
143            updatables: HashMap::default(),
144            largest_file: 0,
145            has_binary_files: false,
146            oldest_change: std::time::SystemTime::UNIX_EPOCH,
147            redundant: Vec::new(),
148            force_rediff: self.force_rediff,
149            deleted_vertices: self.deleted_vertices.clone(),
150            recorded_inodes: self.recorded_inodes.clone(),
151            new_root: self.new_root.clone(),
152        }
153    }
154
155    /// Finish the recording.
156    pub fn finish(mut self) -> Recorded {
157        if self.rec.is_empty() {
158            self.recorded();
159        }
160        let mut it = self.rec.into_iter();
161        let mut result = if let Ok(rec) = Arc::try_unwrap(it.next().unwrap()) {
162            rec.into_inner()
163        } else {
164            unreachable!()
165        };
166        for rec in it {
167            let rec = if let Ok(rec) = Arc::try_unwrap(rec) {
168                rec.into_inner()
169            } else {
170                unreachable!()
171            };
172            let off = result.actions.len();
173            result.actions.extend(rec.actions);
174            for (a, b) in rec.updatables {
175                result.updatables.insert(a + off, b);
176            }
177            result.largest_file = result.largest_file.max(rec.largest_file);
178            result.has_binary_files |= rec.has_binary_files;
179            if result.oldest_change == std::time::UNIX_EPOCH
180                || (rec.oldest_change > std::time::UNIX_EPOCH
181                    && rec.oldest_change < result.oldest_change)
182            {
183                result.oldest_change = rec.oldest_change
184            }
185            result.redundant.extend(rec.redundant)
186        }
187        debug!(
188            "result = {:?}, updatables = {:?}",
189            result.actions, result.updatables
190        );
191        result
192    }
193}
194
195/// An account of the files that have been added, moved or deleted, as
196/// returned by record, and used by apply (when applying a change
197/// created locally) to update the trees and inodes databases.
198#[derive(Debug, Hash, PartialEq, Eq)]
199pub enum InodeUpdate {
200    Add {
201        /// Inode vertex in the graph.
202        pos: ChangePosition,
203        /// `Inode` added by this file addition.
204        inode: Inode,
205    },
206    Deleted {
207        /// `Inode` of the deleted file.
208        inode: Inode,
209    },
210}
211
212#[derive(Debug, Clone)]
213struct RecordItem {
214    v_papa: Position<Option<ChangeId>>,
215    papa: Inode,
216    inode: Inode,
217    basename: String,
218    full_path: String,
219    metadata: InodeMetadata,
220}
221
222impl RecordItem {
223    fn root() -> Self {
224        RecordItem {
225            inode: Inode::ROOT,
226            papa: Inode::ROOT,
227            v_papa: Position::OPTION_ROOT,
228            basename: String::new(),
229            full_path: String::new(),
230            metadata: InodeMetadata::new(0, true),
231        }
232    }
233}
234
235/// Ignore inodes that are in another channel
236#[allow(clippy::type_complexity)]
237fn get_inodes_<T: ChannelTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
238    txn: &ArcTxn<T>,
239    channel: &ChannelRef<T>,
240    inode: &Inode,
241) -> Result<Option<Position<ChangeId>>, RecordError<C::Error, W::Error, T>> {
242    let txn = txn.read();
243    let channel = channel.r.read();
244    Ok(get_inodes::<_, C, W>(&*txn, &*channel, inode)?.copied())
245}
246
247#[allow(clippy::type_complexity)]
248fn get_inodes<'a, T: ChannelTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
249    txn: &'a T,
250    channel: &T::Channel,
251    inode: &Inode,
252) -> Result<Option<&'a Position<ChangeId>>, RecordError<C::Error, W::Error, T>> {
253    if let Some(vertex) = txn.get_inodes(inode, None)? {
254        if let Some(e) = iter_adjacent(
255            txn,
256            txn.graph(channel),
257            vertex.inode_vertex(),
258            EdgeFlags::PARENT,
259            EdgeFlags::all(),
260        )?
261        .next()
262            && e?.flag().is_parent()
263        {
264            return Ok(Some(vertex));
265        }
266        Ok(None)
267    } else {
268        Ok(None)
269    }
270}
271
272type Task = (
273    RecordItem,
274    Position<ChangeId>,
275    Arc<Mutex<Recorded>>,
276    Option<Position<Option<ChangeId>>>,
277);
278
279struct Tasks {
280    stop: bool,
281    t: VecDeque<Task>,
282}
283
284impl Builder {
285    #[allow(clippy::too_many_arguments)]
286    pub fn record<
287        T,
288        W: WorkingCopyRead + Clone + Send + Sync + 'static,
289        C: ChangeStore + Clone + Send + 'static,
290    >(
291        &mut self,
292        txn: ArcTxn<T>,
293        diff_algorithm: diff::Algorithm,
294        stop_early: bool,
295        diff_separator: &regex::bytes::Regex,
296        channel: ChannelRef<T>,
297        working_copy: &W,
298        changes: &C,
299        prefix: &str,
300        _n_workers: usize,
301    ) -> Result<(), RecordError<C::Error, W::Error, T>>
302    where
303        T: ChannelMutTxnT + TreeTxnT + Send + Sync + 'static,
304        T::Channel: Send + Sync,
305        <W as WorkingCopyRead>::Error: 'static,
306    {
307        let work = Arc::new(Mutex::new(Tasks {
308            t: VecDeque::new(),
309            stop: false,
310        }));
311        info!("Starting to record");
312        let now = std::time::Instant::now();
313        let mut stack = vec![(RecordItem::root(), components(prefix))];
314        while let Some((mut item, mut components)) = stack.pop() {
315            debug!("stack.pop() = Some({:?})", item);
316
317            // Check for moves and file conflicts.
318            let vertex: Option<Position<Option<ChangeId>>> =
319                self.recorded_inodes.lock().get(&item.inode).cloned();
320
321            let mut root_vertices = Vec::new();
322
323            let vertex = if let Some(vertex) = vertex {
324                vertex
325            } else if item.inode == Inode::ROOT {
326                debug!("TAKING LOCK {}", line!());
327                let txn = txn.read();
328                debug!("TAKEN");
329                let channel = channel.r.read();
330
331                // Test for a "root" vertex below the null one.
332                let f0 = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
333                let f1 = f0 | EdgeFlags::PSEUDO;
334                self.recorded_inodes
335                    .lock()
336                    .insert(Inode::ROOT, Position::ROOT.to_option());
337                let mut has_nonempty_root = false;
338                for e in iter_adjacent(&*txn, txn.graph(&*channel), Vertex::ROOT, f0, f1)? {
339                    let e = e?;
340                    let child = txn.find_block(txn.graph(&*channel), e.dest()).unwrap();
341                    if child.start == child.end {
342                        // This is the "new" format, with multiple
343                        // roots, and `grandchild` is one of the
344                        // roots.
345                        let grandchild =
346                            iter_adjacent(&*txn, txn.graph(&*channel), *child, f0, f1)?
347                                .next()
348                                .unwrap()?
349                                .dest();
350                        root_vertices.push(grandchild);
351                        self.delete_obsolete_children(
352                            &*txn,
353                            txn.graph(&channel),
354                            working_copy,
355                            changes,
356                            &item.full_path,
357                            grandchild,
358                        )?;
359                    } else {
360                        // Single-root repository, we need to follow
361                        // the root's children.
362                        let mut name = vec![0; child.end - child.start];
363                        changes
364                            .get_contents(
365                                |p| txn.get_external(&p).ok().map(From::from),
366                                *child,
367                                &mut name,
368                            )
369                            .map_err(RecordError::Changestore)?;
370                        debug!("non-empty root {:?} {:?}", child, name);
371                        has_nonempty_root = true
372                    }
373                }
374                debug!("has_nonempty_root: {:?}", has_nonempty_root);
375                debug!("root_vertices: {:?}", root_vertices);
376                if has_nonempty_root && !root_vertices.is_empty() {
377                    // This repository is mixed between "zero" roots,
378                    // and new-style-roots.
379                    root_vertices.push(Position::ROOT)
380                }
381                Position::ROOT.to_option()
382            } else if let Some(vertex) = get_inodes_::<_, C, W>(&txn, &channel, &item.inode)? {
383                {
384                    let txn = txn.read();
385                    let channel = channel.r.read();
386                    let graph = txn.graph(&*channel);
387                    self.delete_obsolete_children(
388                        &*txn,
389                        graph,
390                        working_copy,
391                        changes,
392                        &item.full_path,
393                        vertex,
394                    )?;
395                }
396
397                let rec = self.recorded();
398                let new_papa = {
399                    let mut recorded = self.recorded_inodes.lock();
400                    recorded.insert(item.inode, vertex.to_option());
401                    recorded.get(&item.papa).cloned()
402                };
403                let mut work = work.lock();
404                work.t.push_back((item.clone(), vertex, rec, new_papa));
405                std::mem::drop(work);
406
407                vertex.to_option()
408            } else {
409                let rec = self.recorded();
410                debug!("TAKING LOCK {}", line!());
411                let mut rec = rec.lock();
412                match rec.add_file(working_copy, item.clone()) {
413                    Ok(Some(vertex)) => {
414                        // Path addition (maybe just a single directory).
415                        self.recorded_inodes.lock().insert(item.inode, vertex);
416                        vertex
417                    }
418                    _ => continue,
419                }
420            };
421
422            if root_vertices.is_empty() {
423                // Move on to the next step.
424                debug!("TAKING LOCK {}", line!());
425                let txn = txn.read();
426                let channel = channel.r.read();
427                self.push_children::<_, _, C>(
428                    &*txn,
429                    &*channel,
430                    working_copy,
431                    &mut item,
432                    &mut components,
433                    vertex,
434                    &mut stack,
435                    prefix,
436                    changes,
437                )?;
438            } else {
439                for vertex in root_vertices {
440                    let txn = txn.read();
441                    let channel = channel.r.read();
442                    if !vertex.change.is_root() {
443                        let mut r = self.new_root.lock();
444                        let age = txn
445                            .get_changeset(txn.changes(&*channel), &vertex.change)?
446                            .unwrap();
447                        if let Some((_, a)) = *r {
448                            if a < (*age).into() {
449                                *r = Some((vertex.to_option(), (*age).into()))
450                            }
451                        } else {
452                            *r = Some((vertex.to_option(), (*age).into()))
453                        }
454                    }
455                    item.v_papa = vertex.to_option();
456                    self.push_children::<_, _, C>(
457                        &*txn,
458                        &*channel,
459                        working_copy,
460                        &mut item,
461                        &mut components,
462                        vertex.to_option(),
463                        &mut stack,
464                        prefix,
465                        changes,
466                    )?;
467                }
468            }
469        }
470
471        info!("stop work");
472        work.lock().stop = true;
473        loop {
474            let w = {
475                let mut work = work.lock();
476                debug!("waiting, stop = {:?}", work.stop);
477                work.t.pop_front()
478            };
479            if let Some((item, vertex, rec, new_papa)) = w {
480                // This parent has changed.
481                info!("record existing file {:?}", item);
482                rec.lock().record_existing_file(
483                    &txn,
484                    diff_algorithm,
485                    stop_early,
486                    diff_separator,
487                    &channel,
488                    working_copy.clone(),
489                    changes,
490                    &item,
491                    new_papa,
492                    vertex,
493                )?;
494            } else {
495                break;
496            }
497        }
498
499        crate::TIMERS.lock().unwrap().record += now.elapsed();
500
501        info!("record done");
502        Ok(())
503    }
504
505    #[allow(clippy::too_many_arguments)]
506    pub fn record_single_thread<T, W: WorkingCopyRead + Clone, C: ChangeStore + Clone>(
507        &mut self,
508        txn: ArcTxn<T>,
509        diff_algorithm: diff::Algorithm,
510        stop_early: bool,
511        diff_separator: &regex::bytes::Regex,
512        channel: ChannelRef<T>,
513        working_copy: &W,
514        changes: &C,
515        prefix: &str,
516    ) -> Result<(), RecordError<C::Error, W::Error, T>>
517    where
518        T: ChannelMutTxnT + TreeTxnT,
519        <W as WorkingCopyRead>::Error: 'static,
520    {
521        info!("Starting to record");
522        let now = std::time::Instant::now();
523        let mut stack = vec![(RecordItem::root(), components(prefix))];
524        while let Some((mut item, mut components)) = stack.pop() {
525            debug!("stack.pop() = Some({:?})", item);
526
527            // Check for moves and file conflicts.
528            let vertex: Option<Position<Option<ChangeId>>> =
529                self.recorded_inodes.lock().get(&item.inode).cloned();
530
531            let mut root_vertices = Vec::new();
532
533            let vertex = if let Some(vertex) = vertex {
534                vertex
535            } else if item.inode == Inode::ROOT {
536                debug!("TAKING LOCK {}", line!());
537                let txn = txn.read();
538                debug!("TAKEN");
539                let channel = channel.r.read();
540
541                // Test for a "root" vertex below the null one.
542                let f0 = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
543                let f1 = f0 | EdgeFlags::PSEUDO;
544                self.recorded_inodes
545                    .lock()
546                    .insert(Inode::ROOT, Position::ROOT.to_option());
547                let mut has_nonempty_root = false;
548                for e in iter_adjacent(&*txn, txn.graph(&*channel), Vertex::ROOT, f0, f1)? {
549                    let e = e?;
550                    let child = txn.find_block(txn.graph(&*channel), e.dest()).unwrap();
551                    if child.start == child.end {
552                        // This is the "new" format, with multiple
553                        // roots, and `grandchild` is one of the
554                        // roots.
555                        let grandchild =
556                            iter_adjacent(&*txn, txn.graph(&*channel), *child, f0, f1)?
557                                .next()
558                                .unwrap()?
559                                .dest();
560                        root_vertices.push(grandchild);
561                        self.delete_obsolete_children(
562                            &*txn,
563                            txn.graph(&channel),
564                            working_copy,
565                            changes,
566                            &item.full_path,
567                            grandchild,
568                        )?;
569                    } else {
570                        // Single-root repository, we need to follow
571                        // the root's children.
572                        let mut name = vec![0; child.end - child.start];
573                        changes
574                            .get_contents(
575                                |p| txn.get_external(&p).ok().map(From::from),
576                                *child,
577                                &mut name,
578                            )
579                            .map_err(RecordError::Changestore)?;
580                        debug!("non-empty root {:?} {:?}", child, name);
581                        has_nonempty_root = true
582                    }
583                }
584                debug!("has_nonempty_root: {:?}", has_nonempty_root);
585                debug!("root_vertices: {:?}", root_vertices);
586                if has_nonempty_root && !root_vertices.is_empty() {
587                    // This repository is mixed between "zero" roots,
588                    // and new-style-roots.
589                    root_vertices.push(Position::ROOT)
590                }
591                Position::ROOT.to_option()
592            } else if let Some(vertex) = get_inodes_::<_, C, W>(&txn, &channel, &item.inode)? {
593                {
594                    let txn = txn.read();
595                    let channel = channel.r.read();
596                    let graph = txn.graph(&*channel);
597                    self.delete_obsolete_children(
598                        &*txn,
599                        graph,
600                        working_copy,
601                        changes,
602                        &item.full_path,
603                        vertex,
604                    )?;
605                }
606
607                let rec = self.recorded();
608                let new_papa = {
609                    let mut recorded = self.recorded_inodes.lock();
610                    recorded.insert(item.inode, vertex.to_option());
611                    recorded.get(&item.papa).cloned()
612                };
613
614                rec.lock().record_existing_file(
615                    &txn,
616                    diff_algorithm,
617                    stop_early,
618                    diff_separator,
619                    &channel,
620                    working_copy.clone(),
621                    changes,
622                    &item,
623                    new_papa,
624                    vertex,
625                )?;
626
627                vertex.to_option()
628            } else {
629                let rec = self.recorded();
630                debug!("TAKING LOCK {}", line!());
631                let mut rec = rec.lock();
632                match rec.add_file(working_copy, item.clone()) {
633                    Ok(Some(vertex)) => {
634                        // Path addition (maybe just a single directory).
635                        self.recorded_inodes.lock().insert(item.inode, vertex);
636                        vertex
637                    }
638                    _ => continue,
639                }
640            };
641
642            if root_vertices.is_empty() {
643                // Move on to the next step.
644                debug!("TAKING LOCK {}", line!());
645                let txn = txn.read();
646                let channel = channel.r.read();
647                self.push_children::<_, _, C>(
648                    &*txn,
649                    &*channel,
650                    working_copy,
651                    &mut item,
652                    &mut components,
653                    vertex,
654                    &mut stack,
655                    prefix,
656                    changes,
657                )?;
658            } else {
659                for vertex in root_vertices {
660                    let txn = txn.read();
661                    let channel = channel.r.read();
662                    if !vertex.change.is_root() {
663                        let mut r = self.new_root.lock();
664                        let age = txn
665                            .get_changeset(txn.changes(&*channel), &vertex.change)?
666                            .unwrap();
667                        if let Some((_, a)) = *r {
668                            if a < (*age).into() {
669                                *r = Some((vertex.to_option(), (*age).into()))
670                            }
671                        } else {
672                            *r = Some((vertex.to_option(), (*age).into()))
673                        }
674                    }
675                    item.v_papa = vertex.to_option();
676                    self.push_children::<_, _, C>(
677                        &*txn,
678                        &*channel,
679                        working_copy,
680                        &mut item,
681                        &mut components,
682                        vertex.to_option(),
683                        &mut stack,
684                        prefix,
685                        changes,
686                    )?;
687                }
688            }
689        }
690        crate::TIMERS.lock().unwrap().record += now.elapsed();
691        info!("record done");
692        Ok(())
693    }
694
695    fn delete_obsolete_children<T: GraphTxnT + TreeTxnT, W: WorkingCopyRead, C: ChangeStore>(
696        &mut self,
697        txn: &T,
698        channel: &T::Graph,
699        working_copy: &W,
700        changes: &C,
701        full_path: &str,
702        v: Position<ChangeId>,
703    ) -> Result<(), RecordError<C::Error, W::Error, T>>
704    where
705        <W as WorkingCopyRead>::Error: 'static,
706    {
707        if self.ignore_missing {
708            return Ok(());
709        }
710        let f0 = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
711        let f1 = f0 | EdgeFlags::PSEUDO;
712        debug!("delete_obsolete_children, v = {:?}", v);
713        for child in iter_adjacent(txn, channel, v.inode_vertex(), f0, f1)? {
714            let child = child?;
715            let child = txn.find_block(channel, child.dest()).unwrap();
716            if child.start == child.end {
717                // This is an empty name, i.e. the grandchild is a root vertex.
718                continue;
719            }
720            for grandchild in iter_adjacent(txn, channel, *child, f0, f1)? {
721                let grandchild = grandchild?;
722                debug!("grandchild {:?}", grandchild);
723                let needs_deletion =
724                    if let Some(inode) = txn.get_revinodes(&grandchild.dest(), None)? {
725                        debug!("inode = {:?} {:?}", inode, txn.get_revtree(inode, None));
726                        if let Some(path) = crate::fs::inode_filename(txn, *inode)? {
727                            working_copy.file_metadata(&path).is_err()
728                        } else {
729                            true
730                        }
731                    } else {
732                        true
733                    };
734                if needs_deletion {
735                    let mut name = vec![0; child.end - child.start];
736                    changes
737                        .get_contents(
738                            |p| txn.get_external(&p).ok().map(From::from),
739                            *child,
740                            &mut name,
741                        )
742                        .map_err(RecordError::Changestore)?;
743                    let mut full_path = full_path.to_string();
744                    let meta = FileMetadata::read(&name);
745                    if !full_path.is_empty() {
746                        full_path.push('/');
747                    }
748                    full_path.push_str(meta.basename);
749                    // delete recursively.
750                    let rec = self.recorded();
751                    let mut rec = rec.lock();
752                    rec.record_deleted_file(
753                        txn,
754                        channel,
755                        working_copy,
756                        &full_path,
757                        grandchild.dest(),
758                        changes,
759                    )?
760                }
761            }
762        }
763        Ok(())
764    }
765
766    #[allow(clippy::too_many_arguments)]
767    fn push_children<'a, T: ChannelTxnT + TreeTxnT, W: WorkingCopyRead, C: ChangeStore>(
768        &mut self,
769        txn: &T,
770        channel: &T::Channel,
771        working_copy: &W,
772        item: &mut RecordItem,
773        components: &mut Components<'a>,
774        vertex: Position<Option<ChangeId>>,
775        stack: &mut Vec<(RecordItem, Components<'a>)>,
776        prefix: &str,
777        changes: &C,
778    ) -> Result<(), RecordError<C::Error, W::Error, T>>
779    where
780        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
781    {
782        debug!("push_children, vertex = {:?}, item = {:?}", vertex, item);
783        let comp = components.next();
784        let full_path = item.full_path.clone();
785        let fileid = OwnedPathId {
786            parent_inode: item.inode,
787            basename: SmallString::new(),
788        };
789        debug!("fileid = {:?}", fileid);
790        let mut has_matching_children = false;
791        for x in txn.iter_tree(&fileid, None)? {
792            let (fileid_, child_inode) = x?;
793            debug!("push_children {:?} {:?}", fileid_, child_inode);
794            assert!(fileid_.parent_inode >= fileid.parent_inode);
795            if fileid_.basename.is_empty() {
796                continue;
797            } else if fileid_.parent_inode > fileid.parent_inode {
798                break;
799            }
800            if let Some(comp) = comp
801                && comp != fileid_.basename.as_str()
802            {
803                continue;
804            }
805            has_matching_children = true;
806            let basename = fileid_.basename.as_str().to_string();
807            let full_path = if full_path.is_empty() {
808                basename.clone()
809            } else {
810                full_path.clone() + "/" + &basename
811            };
812            debug!("fileid_ {:?} child_inode {:?}", fileid_, child_inode);
813            match working_copy.file_metadata(&full_path) {
814                Ok(meta) => {
815                    debug!("full_path = {:?}, meta = {:?}", full_path, meta);
816                    stack.push((
817                        RecordItem {
818                            papa: item.inode,
819                            inode: *child_inode,
820                            v_papa: vertex,
821                            basename,
822                            full_path,
823                            metadata: meta,
824                        },
825                        components.clone(),
826                    ));
827                }
828                _ => {
829                    if let Some(vertex) = get_inodes::<_, C, W>(txn, channel, child_inode)? {
830                        let rec = self.recorded();
831                        let mut rec = rec.lock();
832                        rec.record_deleted_file(
833                            txn,
834                            txn.graph(channel),
835                            working_copy,
836                            &full_path,
837                            *vertex,
838                            changes,
839                        )?
840                    }
841                }
842            }
843        }
844        if comp.is_some() && !has_matching_children {
845            debug!("comp = {:?}", comp);
846            return Err(RecordError::PathNotInRepo(prefix.to_string()));
847        }
848        debug!("push_children done");
849        Ok(())
850    }
851}
852
853fn modified_since_last_commit<T: ChannelTxnT, W: WorkingCopyRead>(
854    txn: &T,
855    channel: &T::Channel,
856    working_copy: &W,
857    prefix: &str,
858) -> Result<bool, std::time::SystemTimeError> {
859    match working_copy.modified_time(prefix) {
860        Ok(last_modified) => {
861            debug!(
862                "last_modified = {:?}, channel.last = {:?}",
863                last_modified
864                    .duration_since(std::time::UNIX_EPOCH)?
865                    .as_millis(),
866                txn.last_modified(channel),
867            );
868            // Account for low-resolution filesystems, by truncating the
869            // channel modification time if the file modification time is
870            // a multiple of 1000.
871            let last_mod = last_modified
872                .duration_since(std::time::UNIX_EPOCH)?
873                .as_millis() as u64;
874            let channel_mod = (txn.last_modified(channel) / 1000) * 1000;
875
876            debug!("recording = {:?}", last_mod >= channel_mod);
877            Ok(last_mod >= channel_mod)
878        }
879        _ => Ok(true),
880    }
881}
882
883impl Recorded {
884    fn add_root_if_needed(
885        &mut self,
886        v_papa: Position<Option<ChangeId>>,
887    ) -> Position<Option<ChangeId>> {
888        let mut contents = self.contents.lock();
889        if v_papa.change == Some(ChangeId::ROOT) {
890            let mut new_root = self.new_root.lock();
891            if let Some((pos, _)) = *new_root {
892                pos
893            } else {
894                contents.push(0);
895                let pos = ChangePosition(contents.len().into());
896                contents.push(0);
897                let pos2 = ChangePosition(contents.len().into());
898                contents.push(0);
899                debug!(
900                    "add root if needed {:?} {:?}",
901                    pos.0.as_u64(),
902                    pos2.0.as_u64()
903                );
904                self.actions.push(Hunk::AddRoot {
905                    name: Atom::NewVertex(NewVertex {
906                        up_context: vec![v_papa],
907                        down_context: vec![],
908                        start: pos,
909                        end: pos,
910                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
911                        inode: v_papa,
912                    }),
913                    inode: Atom::NewVertex(NewVertex {
914                        up_context: vec![Position { change: None, pos }],
915                        down_context: vec![],
916                        start: pos2,
917                        end: pos2,
918                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
919                        inode: v_papa,
920                    }),
921                });
922
923                self.updatables.insert(
924                    self.actions.len(),
925                    InodeUpdate::Add {
926                        inode: Inode::ROOT,
927                        pos: pos2,
928                    },
929                );
930
931                *new_root = Some((
932                    Position {
933                        change: None,
934                        pos: pos2,
935                    },
936                    u64::MAX,
937                ));
938                Position {
939                    change: None,
940                    pos: pos2,
941                }
942            }
943        } else {
944            v_papa
945        }
946    }
947
948    fn add_file<W: WorkingCopyRead>(
949        &mut self,
950        working_copy: &W,
951        item: RecordItem,
952    ) -> Result<Option<Position<Option<ChangeId>>>, W::Error> {
953        debug!("record_file_addition {:?}", item);
954        let meta = working_copy.file_metadata(&item.full_path)?;
955
956        // If we're inserting at the root, add an extra "root
957        // directory" empty vertex.
958        let item_v_papa = self.add_root_if_needed(item.v_papa);
959
960        let mut contents = self.contents.lock();
961        contents.push(0);
962        let inode_pos = ChangePosition(contents.len().into());
963        contents.push(0);
964        let (contents_, encoding) = if meta.is_file() {
965            let start = ChangePosition(contents.len().into());
966            let encoding = working_copy.decode_file(&item.full_path, &mut contents)?;
967            self.has_binary_files |= encoding.is_none();
968            let end = ChangePosition(contents.len().into());
969            self.largest_file = self.largest_file.max(end.0.as_u64() - start.0.as_u64());
970            contents.push(0);
971            if end > start {
972                (
973                    Some(Atom::NewVertex(NewVertex {
974                        up_context: vec![Position {
975                            change: None,
976                            pos: inode_pos,
977                        }],
978                        down_context: vec![],
979                        start,
980                        end,
981                        flag: EdgeFlags::BLOCK,
982                        inode: Position {
983                            change: None,
984                            pos: inode_pos,
985                        },
986                    })),
987                    encoding,
988                )
989            } else {
990                (None, encoding)
991            }
992        } else {
993            (None, None)
994        };
995
996        let name_start = ChangePosition(contents.len().into());
997        let file_meta = FileMetadata {
998            metadata: meta,
999            basename: item.basename.as_str(),
1000            encoding: encoding.clone(),
1001        };
1002        file_meta.write(&mut contents);
1003        let name_end = ChangePosition(contents.len().into());
1004        debug!("name start {:?} end {:?}", name_start, name_end);
1005        contents.push(0);
1006        self.actions.push(Hunk::FileAdd {
1007            add_name: Atom::NewVertex(NewVertex {
1008                up_context: vec![item_v_papa],
1009                down_context: vec![],
1010                start: name_start,
1011                end: name_end,
1012                flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1013                inode: item.v_papa,
1014            }),
1015            add_inode: Atom::NewVertex(NewVertex {
1016                up_context: vec![Position {
1017                    change: None,
1018                    pos: name_end,
1019                }],
1020                down_context: vec![],
1021                start: inode_pos,
1022                end: inode_pos,
1023                flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1024                inode: item.v_papa,
1025            }),
1026            contents: contents_,
1027            path: item.full_path.clone(),
1028            encoding,
1029        });
1030        debug!("{:?}", self.actions.last().unwrap());
1031        self.updatables.insert(
1032            self.actions.len(),
1033            InodeUpdate::Add {
1034                inode: item.inode,
1035                pos: inode_pos,
1036            },
1037        );
1038        if meta.is_dir() {
1039            Ok(Some(Position {
1040                change: None,
1041                pos: inode_pos,
1042            }))
1043        } else {
1044            Ok(None)
1045        }
1046    }
1047
1048    #[allow(clippy::too_many_arguments)]
1049    fn record_existing_file<T: ChannelTxnT + TreeTxnT, W: WorkingCopyRead + Clone, C: ChangeStore>(
1050        &mut self,
1051        txn: &ArcTxn<T>,
1052        diff_algorithm: diff::Algorithm,
1053        stop_early: bool,
1054        diff_sep: &regex::bytes::Regex,
1055        channel: &ChannelRef<T>,
1056        working_copy: W,
1057        changes: &C,
1058        item: &RecordItem,
1059        new_papa: Option<Position<Option<ChangeId>>>,
1060        vertex: Position<ChangeId>,
1061    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1062    where
1063        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1064    {
1065        debug!(
1066            "record_existing_file {:?}: {:?} {:?} {:?}",
1067            item.full_path, item.inode, vertex, new_papa,
1068        );
1069        // Former parent(s) of vertex
1070        let (former_parents, is_deleted, encoding) = {
1071            let txn_ = txn.read();
1072            let channel_ = channel.read();
1073            collect_former_parents::<C, W, T>(changes, &*txn_, &*channel_, vertex)?
1074        };
1075        debug!(
1076            "record_existing_file: {:?} {:?} {:?}",
1077            item, former_parents, is_deleted,
1078        );
1079        match working_copy.file_metadata(&item.full_path) {
1080            Ok(new_meta) => self.record_nondeleted(
1081                txn,
1082                diff_algorithm,
1083                stop_early,
1084                diff_sep,
1085                channel,
1086                working_copy,
1087                changes,
1088                item,
1089                new_papa,
1090                vertex,
1091                new_meta,
1092                &former_parents,
1093                is_deleted,
1094                encoding,
1095            )?,
1096            _ => {
1097                debug!("calling record_deleted_file on {:?}", item.full_path);
1098                let txn_ = txn.read();
1099                let channel_ = channel.read();
1100                self.record_deleted_file(
1101                    &*txn_,
1102                    txn_.graph(&*channel_),
1103                    &working_copy,
1104                    &item.full_path,
1105                    vertex,
1106                    changes,
1107                )?
1108            }
1109        }
1110        Ok(())
1111    }
1112
1113    #[allow(clippy::too_many_arguments)]
1114    fn record_nondeleted<T: ChannelTxnT + TreeTxnT, W: WorkingCopyRead + Clone, C: ChangeStore>(
1115        &mut self,
1116        txn: &ArcTxn<T>,
1117        diff_algorithm: diff::Algorithm,
1118        stop_early: bool,
1119        diff_sep: &regex::bytes::Regex,
1120        channel: &ChannelRef<T>,
1121        working_copy: W,
1122        changes: &C,
1123        item: &RecordItem,
1124        new_papa: Option<Position<Option<ChangeId>>>,
1125        vertex: Position<ChangeId>,
1126        new_meta: InodeMetadata,
1127        former_parents: &[Parent],
1128        is_deleted: bool,
1129        encoding: Option<Encoding>,
1130    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1131    where
1132        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1133    {
1134        if former_parents.is_empty() {
1135            // This is the case where the inode exists both in the
1136            // graph and in the inode tables, but isn't alive in the
1137            // graph.
1138            //
1139            // This can happen (1) when outputting a tag that has this
1140            // file, after recording the deletion of the file, or (2)
1141            // when recording after applying, but before outputting,
1142            // but this is a misuse of the library.
1143            debug!("new_papa = {:?}", new_papa);
1144            let txn = txn.read();
1145            let channel = channel.read();
1146            self.record_moved_file::<_, _, W>(
1147                changes,
1148                &*txn,
1149                &*channel,
1150                item,
1151                vertex,
1152                new_papa.unwrap(),
1153                encoding,
1154            )?
1155        } else if former_parents.len() > 1
1156            || former_parents[0].basename != item.basename
1157            || former_parents[0].metadata != item.metadata
1158            || former_parents[0].parent != item.v_papa
1159            || is_deleted
1160        {
1161            debug!("new_papa = {:?}", new_papa);
1162            let txn = txn.read();
1163            let channel = channel.read();
1164            self.record_moved_file::<_, _, W>(
1165                changes,
1166                &*txn,
1167                &*channel,
1168                item,
1169                vertex,
1170                new_papa.unwrap(),
1171                former_parents[0].encoding.clone(),
1172            )?
1173        }
1174        if new_meta.is_file()
1175            && (self.force_rediff
1176                || modified_since_last_commit(
1177                    &*txn.read(),
1178                    &*channel.read(),
1179                    &working_copy,
1180                    &item.full_path,
1181                )?)
1182        {
1183            let mut ret = {
1184                let txn = txn.read();
1185                let channel = channel.read();
1186                retrieve(&*txn, txn.graph(&*channel), vertex, false)?
1187            };
1188            let mut b = Vec::new();
1189            let encoding = working_copy
1190                .decode_file(&item.full_path, &mut b)
1191                .map_err(RecordError::WorkingCopy)?;
1192            debug!("diffing…");
1193            let len = self.actions.len();
1194            self.diff(
1195                changes,
1196                txn,
1197                channel,
1198                diff_algorithm,
1199                stop_early,
1200                item.full_path.clone(),
1201                item.inode,
1202                vertex.to_option(),
1203                &mut ret,
1204                &b,
1205                &encoding,
1206                diff_sep,
1207            )?;
1208            if self.actions.len() > len
1209                && let Ok(last_modified) = working_copy.modified_time(&item.full_path)
1210            {
1211                if self.oldest_change == std::time::SystemTime::UNIX_EPOCH {
1212                    self.oldest_change = last_modified;
1213                } else {
1214                    self.oldest_change = self.oldest_change.min(last_modified);
1215                }
1216            }
1217            debug!(
1218                "new actions: {:?}, total {:?}",
1219                self.actions.len() - len,
1220                self.actions.len()
1221            );
1222        }
1223        Ok(())
1224    }
1225
1226    #[allow(clippy::too_many_arguments)]
1227    fn record_moved_file<T: ChannelTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
1228        &mut self,
1229        changes: &C,
1230        txn: &T,
1231        channel: &T::Channel,
1232        item: &RecordItem,
1233        vertex: Position<ChangeId>,
1234        new_papa: Position<Option<ChangeId>>,
1235        encoding: Option<Encoding>,
1236    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1237    where
1238        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1239    {
1240        debug!("record_moved_file {:?} {:?}", item, vertex);
1241        let basename = item.basename.as_str();
1242        let mut moved = collect_moved_edges::<_, _, W>(
1243            txn,
1244            changes,
1245            txn.graph(channel),
1246            new_papa,
1247            vertex,
1248            item.metadata,
1249            basename,
1250        )?;
1251        debug!("moved = {:#?}", moved);
1252        let is_resurrected = !moved.resurrect.is_empty();
1253        if is_resurrected {
1254            moved.resurrect.append(&mut moved.alive);
1255            if !moved.need_new_name {
1256                moved.resurrect.append(&mut moved.edges);
1257            }
1258            self.actions.push(Hunk::FileUndel {
1259                undel: Atom::EdgeMap(EdgeMap {
1260                    edges: moved.resurrect,
1261                    inode: item.v_papa,
1262                }),
1263                contents: None,
1264                path: item.full_path.clone(),
1265                encoding: encoding.clone(),
1266            });
1267        }
1268
1269        let item_v_papa = if !moved.edges.is_empty() && moved.need_new_name {
1270            self.add_root_if_needed(item.v_papa)
1271        } else {
1272            item.v_papa
1273        };
1274
1275        let mut contents = self.contents.lock();
1276        contents.push(0);
1277        let meta_start = ChangePosition(contents.len().into());
1278        FileMetadata {
1279            metadata: item.metadata,
1280            basename,
1281            encoding: encoding.clone(),
1282        }
1283        .write(&mut contents);
1284        let meta_end = ChangePosition(contents.len().into());
1285        contents.push(0);
1286        if !moved.edges.is_empty() {
1287            // If there was exactly one alive name, this is a regular
1288            // move, i.e. not a conflict.
1289            if moved.n_alive_names == 1 || (moved.need_new_name && !is_resurrected) {
1290                debug!("need_new_name {:?}", item.v_papa);
1291                let add = if moved.need_new_name && !is_resurrected {
1292                    moved.edges.append(&mut moved.alive);
1293                    Atom::NewVertex(NewVertex {
1294                        up_context: vec![item_v_papa],
1295                        down_context: vec![vertex.to_option()],
1296                        start: meta_start,
1297                        end: meta_end,
1298                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1299                        inode: item_v_papa,
1300                    })
1301                } else {
1302                    Atom::EdgeMap(EdgeMap {
1303                        edges: moved.alive,
1304                        inode: item_v_papa,
1305                    })
1306                };
1307                self.actions.push(Hunk::FileMove {
1308                    del: Atom::EdgeMap(EdgeMap {
1309                        edges: moved.edges,
1310                        inode: item.v_papa,
1311                    }),
1312                    add,
1313                    path: crate::fs::find_path(changes, txn, channel, true, vertex)?
1314                        .unwrap()
1315                        .path
1316                        .join("/"),
1317                });
1318            } else {
1319                self.actions.push(Hunk::SolveNameConflict {
1320                    name: Atom::EdgeMap(EdgeMap {
1321                        edges: moved.edges,
1322                        inode: item.v_papa,
1323                    }),
1324                    path: item.full_path.clone(),
1325                });
1326                contents.truncate(meta_start.0.as_usize())
1327            }
1328        } else {
1329            contents.truncate(meta_start.0.as_usize())
1330        }
1331        Ok(())
1332    }
1333
1334    pub fn take_updatables(&mut self) -> HashMap<usize, InodeUpdate> {
1335        std::mem::take(&mut self.updatables)
1336    }
1337
1338    pub fn into_change<T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>>(
1339        self,
1340        txn: &T,
1341        channel: &ChannelRef<T>,
1342        header: crate::change::ChangeHeader,
1343    ) -> Result<crate::change::LocalChangeLocal, crate::change::MakeChangeError<T>> {
1344        let actions = self
1345            .actions
1346            .into_iter()
1347            .map(|rec| rec.globalize(txn).unwrap())
1348            .collect();
1349        let contents = if let Ok(c) = Arc::try_unwrap(self.contents) {
1350            c.into_inner()
1351        } else {
1352            unreachable!()
1353        };
1354        crate::change::LocalChange::make_change(txn, channel, actions, contents, header, Vec::new())
1355    }
1356}
1357
1358#[allow(clippy::type_complexity)]
1359fn collect_former_parents<C: ChangeStore, W: WorkingCopyRead, T: ChannelTxnT + TreeTxnT>(
1360    changes: &C,
1361    txn: &T,
1362    channel: &T::Channel,
1363    vertex: Position<ChangeId>,
1364) -> Result<(Vec<Parent>, bool, Option<Encoding>), RecordError<C::Error, W::Error, T>>
1365where
1366    W::Error: 'static,
1367{
1368    let mut former_parents = Vec::new();
1369    let f0 = EdgeFlags::FOLDER | EdgeFlags::PARENT;
1370    let f1 = EdgeFlags::all();
1371    let mut is_deleted = true;
1372    let mut encoding_ = None;
1373    for name_ in iter_adjacent(txn, txn.graph(channel), vertex.inode_vertex(), f0, f1)? {
1374        debug!("name_ = {:?}", name_);
1375        let name_ = name_?;
1376        if !name_.flag().contains(EdgeFlags::PARENT) {
1377            debug!("continue");
1378            continue;
1379        }
1380
1381        let name_dest = txn
1382            .find_block_end(txn.graph(channel), name_.dest())
1383            .unwrap();
1384        let mut meta = vec![0; name_dest.end - name_dest.start];
1385        let FileMetadata {
1386            basename,
1387            metadata,
1388            encoding,
1389        } = changes
1390            .get_file_meta(
1391                |p| txn.get_external(&p).ok().map(From::from),
1392                *name_dest,
1393                &mut meta,
1394            )
1395            .map_err(RecordError::Changestore)?;
1396        debug!(
1397            "former basename of {:?}: {:?} {:?}",
1398            vertex, basename, metadata
1399        );
1400
1401        if name_.flag().contains(EdgeFlags::DELETED) {
1402            debug!("is_deleted {:?}", name_);
1403            is_deleted = true;
1404            if encoding_.is_none() {
1405                encoding_ = encoding
1406            }
1407            break;
1408        }
1409        if let Some(v_papa) = iter_adjacent(txn, txn.graph(channel), *name_dest, f0, f1)?.next() {
1410            let v_papa = v_papa?;
1411            if !v_papa.flag().contains(EdgeFlags::DELETED) {
1412                if encoding_.is_none() {
1413                    encoding_ = encoding.clone()
1414                }
1415                former_parents.push(Parent {
1416                    basename: basename.to_string(),
1417                    metadata,
1418                    encoding,
1419                    parent: v_papa.dest().to_option(),
1420                })
1421            }
1422        }
1423    }
1424    Ok((former_parents, is_deleted, encoding_))
1425}
1426
1427#[derive(Debug)]
1428struct MovedEdges {
1429    edges: Vec<NewEdge<Option<ChangeId>>>,
1430    alive: Vec<NewEdge<Option<ChangeId>>>,
1431    resurrect: Vec<NewEdge<Option<ChangeId>>>,
1432    need_new_name: bool,
1433    n_alive_names: usize,
1434}
1435
1436fn collect_moved_edges<T: GraphTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
1437    txn: &T,
1438    changes: &C,
1439    channel: &T::Graph,
1440    parent_pos: Position<Option<ChangeId>>,
1441    current_pos: Position<ChangeId>,
1442    new_meta: InodeMetadata,
1443    name: &str,
1444) -> Result<MovedEdges, RecordError<C::Error, W::Error, T>>
1445where
1446    <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1447{
1448    debug!("collect_moved_edges {:?}", current_pos);
1449    let mut moved = MovedEdges {
1450        edges: Vec::new(),
1451        alive: Vec::new(),
1452        resurrect: Vec::new(),
1453        need_new_name: true,
1454        n_alive_names: 0,
1455    };
1456    let mut del_del = HashMap::default();
1457    let mut alive = HashMap::default();
1458    let mut previous_name = Vec::new();
1459    let mut last_alive_meta = None;
1460    let mut is_first_parent = true;
1461    for parent in iter_adjacent(
1462        txn,
1463        channel,
1464        current_pos.inode_vertex(),
1465        EdgeFlags::FOLDER | EdgeFlags::PARENT,
1466        EdgeFlags::all(),
1467    )? {
1468        let parent = parent?;
1469        if !parent
1470            .flag()
1471            .contains(EdgeFlags::FOLDER | EdgeFlags::PARENT)
1472        {
1473            continue;
1474        }
1475        debug!("parent = {:?}", parent);
1476        let mut parent_was_resurrected = false;
1477        if !parent.flag().contains(EdgeFlags::PSEUDO) {
1478            if parent.flag().contains(EdgeFlags::DELETED) {
1479                debug!("resurrecting parent");
1480                moved.resurrect.push(NewEdge {
1481                    previous: parent.flag() - EdgeFlags::PARENT,
1482                    flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1483                    from: parent.dest().to_option(),
1484                    to: current_pos.inode_vertex().to_option(),
1485                    introduced_by: Some(parent.introduced_by()),
1486                });
1487                parent_was_resurrected = true;
1488                let v = alive
1489                    .entry((parent.dest(), current_pos.inode_vertex()))
1490                    .or_insert_with(HashSet::new);
1491                v.insert(None);
1492            } else {
1493                let v = alive
1494                    .entry((parent.dest(), current_pos.inode_vertex()))
1495                    .or_insert_with(HashSet::new);
1496                v.insert(Some(parent.introduced_by()));
1497            }
1498        }
1499        debug!("parent_was_resurrected: {:?}", parent_was_resurrected);
1500        let parent_dest = txn.find_block_end(channel, parent.dest()).unwrap();
1501        previous_name.resize(parent_dest.end - parent_dest.start, 0);
1502        let FileMetadata {
1503            metadata: parent_meta,
1504            basename: parent_name,
1505            ..
1506        } = changes
1507            .get_file_meta(
1508                |p| txn.get_external(&p).ok().map(From::from),
1509                *parent_dest,
1510                &mut previous_name,
1511            )
1512            .map_err(RecordError::Changestore)?;
1513        debug!(
1514            "parent_dest {:?} {:?} {:?} {:?}",
1515            parent_dest, parent_meta, parent_name, name
1516        );
1517        let name_changed = parent_name != name;
1518        let mut meta_changed = new_meta != parent_meta;
1519        if cfg!(windows)
1520            && !meta_changed
1521            && let Some(m) = last_alive_meta
1522        {
1523            meta_changed = new_meta != m
1524        }
1525        let mut name_is_alive = false;
1526        for grandparent in iter_adjacent(
1527            txn,
1528            channel,
1529            *parent_dest,
1530            EdgeFlags::FOLDER | EdgeFlags::PARENT,
1531            EdgeFlags::all(),
1532        )? {
1533            let grandparent = grandparent?;
1534            if !grandparent
1535                .flag()
1536                .contains(EdgeFlags::FOLDER | EdgeFlags::PARENT)
1537                || grandparent.flag().contains(EdgeFlags::PSEUDO)
1538            {
1539                continue;
1540            }
1541            debug!("grandparent: {:?}", grandparent);
1542            let grandparent_dest = txn.find_block_end(channel, grandparent.dest()).unwrap();
1543            assert_eq!(grandparent_dest.start, grandparent_dest.end);
1544            debug!(
1545                "grandparent_dest {:?} {:?}, parent_pos = {:?}",
1546                grandparent_dest,
1547                std::str::from_utf8(&previous_name[2..]),
1548                parent_pos,
1549            );
1550            let grandparent_changed = if parent_pos.change == Some(ChangeId::ROOT) {
1551                // Because repos may have multiple roots there may be
1552                // a mismatch here.  The "no change" case when
1553                // `parent_pos` is a root vertex is when
1554                // `grandparent.dest()` is also a root vertex.
1555                !is_root_vertex(txn, channel, grandparent.dest())?
1556            } else {
1557                parent_pos != grandparent.dest().to_option()
1558            };
1559            debug!(
1560                "change = {:?} {:?} {:?}",
1561                grandparent_changed, name_changed, meta_changed
1562            );
1563            if !grandparent.flag().contains(EdgeFlags::DELETED) {
1564                name_is_alive = true
1565            }
1566            if grandparent.flag().contains(EdgeFlags::DELETED) {
1567                if !grandparent_changed && !name_changed && !meta_changed {
1568                    // We resurrect the name
1569                    (if parent_was_resurrected {
1570                        &mut moved.resurrect
1571                    } else {
1572                        &mut moved.alive
1573                    })
1574                    .push(NewEdge {
1575                        previous: grandparent.flag() - EdgeFlags::PARENT,
1576                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1577                        from: grandparent.dest().to_option(),
1578                        to: parent_dest.to_option(),
1579                        introduced_by: Some(grandparent.introduced_by()),
1580                    });
1581                    if !parent_was_resurrected && !parent.flag().contains(EdgeFlags::PSEUDO) {
1582                        moved.alive.push(NewEdge {
1583                            previous: parent.flag() - EdgeFlags::PARENT,
1584                            flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1585                            from: parent.dest().to_option(),
1586                            to: current_pos.inode_vertex().to_option(),
1587                            introduced_by: Some(parent.introduced_by()),
1588                        })
1589                    }
1590                    moved.need_new_name = false;
1591                    // We've found an alive parent, delete the others.
1592                    is_first_parent = false;
1593                } else {
1594                    // Clean up the extra deleted edges.
1595                    debug!("cleanup");
1596                    let v = del_del
1597                        .entry((grandparent.dest(), parent_dest))
1598                        .or_insert_with(HashSet::new);
1599                    v.insert(Some(grandparent.introduced_by()));
1600                }
1601            } else if grandparent_changed
1602                || name_changed
1603                || (meta_changed && cfg!(unix))
1604                || !is_first_parent
1605            {
1606                if !parent.flag().contains(EdgeFlags::PSEUDO) {
1607                    moved.edges.push(NewEdge {
1608                        previous: parent.flag() - EdgeFlags::PARENT - EdgeFlags::PSEUDO,
1609                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1610                        from: grandparent.dest().to_option(),
1611                        to: parent_dest.to_option(),
1612                        introduced_by: Some(grandparent.introduced_by()),
1613                    });
1614                    // The following extra edge is meant to allow
1615                    // detection of missing contexts in folders: indeed,
1616                    // if we didn't have it, we couldn't tell the
1617                    // difference between a convergent renaming or
1618                    // deletion and a conflict between a renaming and a
1619                    // deletion.\
1620                    if !parent_was_resurrected {
1621                        moved.alive.push(NewEdge {
1622                            previous: parent.flag() - EdgeFlags::PARENT,
1623                            flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1624                            from: parent.dest().to_option(),
1625                            to: current_pos.inode_vertex().to_option(),
1626                            introduced_by: Some(parent.introduced_by()),
1627                        })
1628                    }
1629                }
1630            } else {
1631                last_alive_meta = Some(new_meta);
1632                let v = alive
1633                    .entry((grandparent.dest(), *parent_dest))
1634                    .or_insert_with(HashSet::new);
1635                v.insert(Some(grandparent.introduced_by()));
1636                moved.need_new_name = false;
1637                // We've found an alive parent, delete the others.
1638                is_first_parent = false;
1639            }
1640        }
1641        if name_is_alive {
1642            moved.n_alive_names += 1
1643        }
1644    }
1645
1646    for ((from, to), intro) in del_del {
1647        if intro.len() > 1 {
1648            for introduced_by in intro {
1649                if introduced_by.is_some() {
1650                    moved.edges.push(NewEdge {
1651                        previous: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1652                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1653                        from: from.to_option(),
1654                        to: to.to_option(),
1655                        introduced_by,
1656                    })
1657                }
1658            }
1659        }
1660    }
1661
1662    debug!("alive = {:#?}", alive);
1663
1664    for ((from, to), intro) in alive {
1665        if intro.len() > 1 || !moved.resurrect.is_empty() {
1666            for introduced_by in intro {
1667                if introduced_by.is_some() {
1668                    moved.alive.push(NewEdge {
1669                        previous: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1670                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1671                        from: from.to_option(),
1672                        to: to.to_option(),
1673                        introduced_by,
1674                    })
1675                }
1676            }
1677        }
1678    }
1679
1680    Ok(moved)
1681}
1682
1683/// Is this vertex a (potentially deleted) "root vertex", i.e. a root
1684/// of the file hierarchy?
1685fn is_root_vertex<T: GraphTxnT>(
1686    txn: &T,
1687    channel: &T::Graph,
1688    v: Position<ChangeId>,
1689) -> Result<bool, TxnErr<T::GraphError>> {
1690    for parent in iter_adjacent(
1691        txn,
1692        channel,
1693        v.inode_vertex(),
1694        EdgeFlags::FOLDER | EdgeFlags::PARENT,
1695        EdgeFlags::all(),
1696    )? {
1697        let parent = parent?;
1698        if !parent.flag().contains(EdgeFlags::PARENT) {
1699            continue;
1700        }
1701        let p = parent.dest();
1702        let p = txn.find_block_end(channel, p).unwrap();
1703        if p.start == p.end {
1704            return Ok(true);
1705        } else {
1706            return Ok(false);
1707        }
1708    }
1709    Ok(false)
1710}
1711
1712impl Recorded {
1713    fn record_deleted_file<T: GraphTxnT + TreeTxnT, W: WorkingCopyRead, C: ChangeStore>(
1714        &mut self,
1715        txn: &T,
1716        channel: &T::Graph,
1717        working_copy: &W,
1718        full_path: &str,
1719        current_vertex: Position<ChangeId>,
1720        changes: &C,
1721    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1722    where
1723        <W as WorkingCopyRead>::Error: 'static,
1724    {
1725        debug!("record_deleted_file {:?} {:?}", current_vertex, full_path);
1726        let mut stack = vec![(current_vertex.inode_vertex(), None)];
1727        let mut visited = HashSet::default();
1728        let mut full_path = std::borrow::Cow::Borrowed(full_path);
1729        while let Some((vertex, inode)) = stack.pop() {
1730            debug!("vertex {:?}, inode {:?}", vertex, inode);
1731            if let Some(path) = tree_path(txn, &vertex.start_pos())? {
1732                if working_copy.file_metadata(&path).is_ok() {
1733                    debug!("not deleting {:?}", path);
1734                    continue;
1735                }
1736                full_path = path.into()
1737            }
1738
1739            // Kill this vertex
1740            if let Some(inode) = inode {
1741                self.delete_file_edge(txn, channel, vertex, inode)?
1742            } else if vertex.start == vertex.end {
1743                debug!("delete_recursively {:?}", vertex);
1744                // Killing an inode.
1745                {
1746                    let mut deleted_vertices = self.deleted_vertices.lock();
1747                    if !deleted_vertices.insert(vertex.start_pos()) {
1748                        continue;
1749                    }
1750                }
1751                if let Some(inode) = txn.get_revinodes(&vertex.start_pos(), None)? {
1752                    debug!(
1753                        "delete_recursively, vertex = {:?}, inode = {:?}",
1754                        vertex, inode
1755                    );
1756                    self.recorded_inodes
1757                        .lock()
1758                        .insert(*inode, vertex.start_pos().to_option());
1759                    self.updatables.insert(
1760                        self.actions.len() + 1,
1761                        InodeUpdate::Deleted { inode: *inode },
1762                    );
1763                }
1764                self.delete_inode_vertex::<_, _, W>(
1765                    changes,
1766                    txn,
1767                    channel,
1768                    vertex,
1769                    vertex.start_pos(),
1770                    &full_path,
1771                )?
1772            }
1773
1774            // Move on to the descendants.
1775            for edge in iter_adjacent(
1776                txn,
1777                channel,
1778                vertex,
1779                EdgeFlags::empty(),
1780                EdgeFlags::all() - EdgeFlags::DELETED - EdgeFlags::PARENT,
1781            )? {
1782                let edge = edge?;
1783                debug!("delete_recursively, edge: {:?}", edge);
1784                let dest = txn
1785                    .find_block(channel, edge.dest())
1786                    .expect("delete_recursively, descendants");
1787                let inode = if inode.is_some() {
1788                    assert!(!edge.flag().contains(EdgeFlags::FOLDER));
1789                    inode
1790                } else if edge.flag().contains(EdgeFlags::FOLDER) {
1791                    None
1792                } else {
1793                    assert_eq!(vertex.start, vertex.end);
1794                    Some(vertex.start_pos())
1795                };
1796                if visited.insert(edge.dest()) {
1797                    stack.push((*dest, inode))
1798                }
1799            }
1800        }
1801        Ok(())
1802    }
1803
1804    fn delete_inode_vertex<T: GraphTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
1805        &mut self,
1806        changes: &C,
1807        txn: &T,
1808        channel: &T::Graph,
1809        vertex: Vertex<ChangeId>,
1810        inode: Position<ChangeId>,
1811        path: &str,
1812    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1813    where
1814        <W as WorkingCopyRead>::Error: 'static,
1815    {
1816        debug!("delete_inode_vertex {:?}", path);
1817        let mut edges = Vec::new();
1818        let mut enc = None;
1819        let mut previous_name = Vec::new();
1820        for parent in iter_adjacent(
1821            txn,
1822            channel,
1823            vertex,
1824            EdgeFlags::FOLDER | EdgeFlags::PARENT,
1825            EdgeFlags::all(),
1826        )? {
1827            let parent = parent?;
1828            if !parent.flag().contains(EdgeFlags::PARENT) {
1829                continue;
1830            }
1831            assert!(parent.flag().contains(EdgeFlags::FOLDER));
1832            let parent_dest = txn.find_block_end(channel, parent.dest()).unwrap();
1833            if enc.is_none() {
1834                previous_name.resize(parent_dest.end - parent_dest.start, 0);
1835                let FileMetadata { encoding, .. } = changes
1836                    .get_file_meta(
1837                        |p| txn.get_external(&p).ok().map(From::from),
1838                        *parent_dest,
1839                        &mut previous_name,
1840                    )
1841                    .map_err(RecordError::Changestore)?;
1842                enc = Some(encoding);
1843            }
1844
1845            for grandparent in iter_adjacent(
1846                txn,
1847                channel,
1848                *parent_dest,
1849                EdgeFlags::FOLDER | EdgeFlags::PARENT,
1850                EdgeFlags::all(),
1851            )? {
1852                let grandparent = grandparent?;
1853                if !grandparent.flag().contains(EdgeFlags::PARENT)
1854                    || grandparent.flag().contains(EdgeFlags::PSEUDO)
1855                {
1856                    continue;
1857                }
1858                assert!(grandparent.flag().contains(EdgeFlags::PARENT));
1859                assert!(grandparent.flag().contains(EdgeFlags::FOLDER));
1860                edges.push(NewEdge {
1861                    previous: grandparent.flag() - EdgeFlags::PARENT,
1862                    flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1863                    from: grandparent.dest().to_option(),
1864                    to: parent_dest.to_option(),
1865                    introduced_by: Some(grandparent.introduced_by()),
1866                });
1867            }
1868            if !parent.flag().contains(EdgeFlags::PSEUDO) {
1869                edges.push(NewEdge {
1870                    previous: parent.flag() - EdgeFlags::PARENT,
1871                    flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1872                    from: parent.dest().to_option(),
1873                    to: vertex.to_option(),
1874                    introduced_by: Some(parent.introduced_by()),
1875                });
1876            }
1877        }
1878        debug!("deleting {:?}", edges);
1879        if !edges.is_empty() {
1880            self.actions.push(Hunk::FileDel {
1881                del: Atom::EdgeMap(EdgeMap {
1882                    edges,
1883                    inode: inode.to_option(),
1884                }),
1885                contents: None,
1886                path: path.to_string(),
1887                encoding: enc.unwrap(),
1888            })
1889        }
1890        Ok(())
1891    }
1892
1893    fn delete_file_edge<T: GraphTxnT>(
1894        &mut self,
1895        txn: &T,
1896        channel: &T::Graph,
1897        to: Vertex<ChangeId>,
1898        inode: Position<ChangeId>,
1899    ) -> Result<(), TxnErr<T::GraphError>> {
1900        if let Some(Hunk::FileDel { contents, .. }) = self.actions.last_mut() {
1901            if contents.is_none() {
1902                *contents = Some(Atom::EdgeMap(EdgeMap {
1903                    edges: Vec::new(),
1904                    inode: inode.to_option(),
1905                }))
1906            }
1907            if let Some(Atom::EdgeMap(mut e)) = contents.take() {
1908                for parent in iter_adjacent(
1909                    txn,
1910                    channel,
1911                    to,
1912                    EdgeFlags::PARENT,
1913                    EdgeFlags::all() - EdgeFlags::DELETED,
1914                )? {
1915                    let parent = parent?;
1916                    if parent.flag().contains(EdgeFlags::PSEUDO) {
1917                        continue;
1918                    }
1919                    assert!(parent.flag().contains(EdgeFlags::PARENT));
1920                    assert!(!parent.flag().contains(EdgeFlags::FOLDER));
1921                    e.edges.push(NewEdge {
1922                        previous: parent.flag() - EdgeFlags::PARENT,
1923                        flag: (parent.flag() - EdgeFlags::PARENT) | EdgeFlags::DELETED,
1924                        from: parent.dest().to_option(),
1925                        to: to.to_option(),
1926                        introduced_by: Some(parent.introduced_by()),
1927                    })
1928                }
1929                if !e.edges.is_empty() {
1930                    *contents = Some(Atom::EdgeMap(e))
1931                }
1932            }
1933        } else {
1934            unreachable!()
1935        }
1936        Ok(())
1937    }
1938}