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            debug!("find block {:?} {:?}", child.dest(), child.introduced_by());
716            let child = txn.find_block(channel, child.dest()).unwrap();
717            if child.start == child.end {
718                // This is an empty name, i.e. the grandchild is a root vertex.
719                continue;
720            }
721            for grandchild in iter_adjacent(txn, channel, *child, f0, f1)? {
722                let grandchild = grandchild?;
723                debug!("grandchild {:?}", grandchild);
724                let needs_deletion =
725                    if let Some(inode) = txn.get_revinodes(&grandchild.dest(), None)? {
726                        debug!("inode = {:?} {:?}", inode, txn.get_revtree(inode, None));
727                        if let Some(path) = crate::fs::inode_filename(txn, *inode)? {
728                            working_copy.file_metadata(&path).is_err()
729                        } else {
730                            true
731                        }
732                    } else {
733                        true
734                    };
735                if needs_deletion {
736                    let mut name = vec![0; child.end - child.start];
737                    changes
738                        .get_contents(
739                            |p| txn.get_external(&p).ok().map(From::from),
740                            *child,
741                            &mut name,
742                        )
743                        .map_err(RecordError::Changestore)?;
744                    let mut full_path = full_path.to_string();
745                    let meta = FileMetadata::read(&name);
746                    if !full_path.is_empty() {
747                        full_path.push('/');
748                    }
749                    full_path.push_str(meta.basename);
750                    // delete recursively.
751                    let rec = self.recorded();
752                    let mut rec = rec.lock();
753                    rec.record_deleted_file(
754                        txn,
755                        channel,
756                        working_copy,
757                        &full_path,
758                        grandchild.dest(),
759                        changes,
760                    )?
761                }
762            }
763        }
764        Ok(())
765    }
766
767    #[allow(clippy::too_many_arguments)]
768    fn push_children<'a, T: ChannelTxnT + TreeTxnT, W: WorkingCopyRead, C: ChangeStore>(
769        &mut self,
770        txn: &T,
771        channel: &T::Channel,
772        working_copy: &W,
773        item: &mut RecordItem,
774        components: &mut Components<'a>,
775        vertex: Position<Option<ChangeId>>,
776        stack: &mut Vec<(RecordItem, Components<'a>)>,
777        prefix: &str,
778        changes: &C,
779    ) -> Result<(), RecordError<C::Error, W::Error, T>>
780    where
781        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
782    {
783        debug!("push_children, vertex = {:?}, item = {:?}", vertex, item);
784        let comp = components.next();
785        let full_path = item.full_path.clone();
786        let fileid = OwnedPathId {
787            parent_inode: item.inode,
788            basename: SmallString::new(),
789        };
790        debug!("fileid = {:?}", fileid);
791        let mut has_matching_children = false;
792        for x in txn.iter_tree(&fileid, None)? {
793            let (fileid_, child_inode) = x?;
794            debug!("push_children {:?} {:?}", fileid_, child_inode);
795            assert!(fileid_.parent_inode >= fileid.parent_inode);
796            if fileid_.basename.is_empty() {
797                continue;
798            } else if fileid_.parent_inode > fileid.parent_inode {
799                break;
800            }
801            if let Some(comp) = comp
802                && comp != fileid_.basename.as_str()
803            {
804                continue;
805            }
806            has_matching_children = true;
807            let basename = fileid_.basename.as_str().to_string();
808            let full_path = if full_path.is_empty() {
809                basename.clone()
810            } else {
811                full_path.clone() + "/" + &basename
812            };
813            debug!("fileid_ {:?} child_inode {:?}", fileid_, child_inode);
814            match working_copy.file_metadata(&full_path) {
815                Ok(meta) => {
816                    debug!("full_path = {:?}, meta = {:?}", full_path, meta);
817                    stack.push((
818                        RecordItem {
819                            papa: item.inode,
820                            inode: *child_inode,
821                            v_papa: vertex,
822                            basename,
823                            full_path,
824                            metadata: meta,
825                        },
826                        components.clone(),
827                    ));
828                }
829                _ => {
830                    if let Some(vertex) = get_inodes::<_, C, W>(txn, channel, child_inode)? {
831                        let rec = self.recorded();
832                        let mut rec = rec.lock();
833                        rec.record_deleted_file(
834                            txn,
835                            txn.graph(channel),
836                            working_copy,
837                            &full_path,
838                            *vertex,
839                            changes,
840                        )?
841                    }
842                }
843            }
844        }
845        if comp.is_some() && !has_matching_children {
846            debug!("comp = {:?}", comp);
847            return Err(RecordError::PathNotInRepo(prefix.to_string()));
848        }
849        debug!("push_children done");
850        Ok(())
851    }
852}
853
854fn modified_since_last_commit<T: ChannelTxnT, W: WorkingCopyRead>(
855    txn: &T,
856    channel: &T::Channel,
857    working_copy: &W,
858    prefix: &str,
859) -> Result<bool, std::time::SystemTimeError> {
860    match working_copy.modified_time(prefix) {
861        Ok(last_modified) => {
862            debug!(
863                "last_modified = {:?}, channel.last = {:?}",
864                last_modified
865                    .duration_since(std::time::UNIX_EPOCH)?
866                    .as_millis(),
867                txn.last_modified(channel),
868            );
869            // Account for low-resolution filesystems, by truncating the
870            // channel modification time if the file modification time is
871            // a multiple of 1000.
872            let last_mod = last_modified
873                .duration_since(std::time::UNIX_EPOCH)?
874                .as_millis() as u64;
875            let channel_mod = (txn.last_modified(channel) / 1000) * 1000;
876
877            debug!("recording = {:?}", last_mod >= channel_mod);
878            Ok(last_mod >= channel_mod)
879        }
880        _ => Ok(true),
881    }
882}
883
884impl Recorded {
885    fn add_root_if_needed(
886        &mut self,
887        v_papa: Position<Option<ChangeId>>,
888    ) -> Position<Option<ChangeId>> {
889        let mut contents = self.contents.lock();
890        if v_papa.change == Some(ChangeId::ROOT) {
891            let mut new_root = self.new_root.lock();
892            if let Some((pos, _)) = *new_root {
893                pos
894            } else {
895                contents.push(0);
896                let pos = ChangePosition(contents.len().into());
897                contents.push(0);
898                let pos2 = ChangePosition(contents.len().into());
899                contents.push(0);
900                debug!(
901                    "add root if needed {:?} {:?}",
902                    pos.0.as_u64(),
903                    pos2.0.as_u64()
904                );
905                self.actions.push(Hunk::AddRoot {
906                    name: Atom::NewVertex(NewVertex {
907                        up_context: vec![v_papa],
908                        down_context: vec![],
909                        start: pos,
910                        end: pos,
911                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
912                        inode: v_papa,
913                    }),
914                    inode: Atom::NewVertex(NewVertex {
915                        up_context: vec![Position { change: None, pos }],
916                        down_context: vec![],
917                        start: pos2,
918                        end: pos2,
919                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
920                        inode: v_papa,
921                    }),
922                });
923
924                self.updatables.insert(
925                    self.actions.len(),
926                    InodeUpdate::Add {
927                        inode: Inode::ROOT,
928                        pos: pos2,
929                    },
930                );
931
932                *new_root = Some((
933                    Position {
934                        change: None,
935                        pos: pos2,
936                    },
937                    u64::MAX,
938                ));
939                Position {
940                    change: None,
941                    pos: pos2,
942                }
943            }
944        } else {
945            v_papa
946        }
947    }
948
949    fn add_file<W: WorkingCopyRead>(
950        &mut self,
951        working_copy: &W,
952        item: RecordItem,
953    ) -> Result<Option<Position<Option<ChangeId>>>, W::Error> {
954        debug!("record_file_addition {:?}", item);
955        let meta = working_copy.file_metadata(&item.full_path)?;
956
957        // If we're inserting at the root, add an extra "root
958        // directory" empty vertex.
959        let item_v_papa = self.add_root_if_needed(item.v_papa);
960
961        let mut contents = self.contents.lock();
962        contents.push(0);
963        let inode_pos = ChangePosition(contents.len().into());
964        contents.push(0);
965        let (contents_, encoding) = if meta.is_file() {
966            let start = ChangePosition(contents.len().into());
967            let encoding = working_copy.decode_file(&item.full_path, &mut contents)?;
968            self.has_binary_files |= encoding.is_none();
969            let end = ChangePosition(contents.len().into());
970            self.largest_file = self.largest_file.max(end.0.as_u64() - start.0.as_u64());
971            contents.push(0);
972            if end > start {
973                (
974                    Some(Atom::NewVertex(NewVertex {
975                        up_context: vec![Position {
976                            change: None,
977                            pos: inode_pos,
978                        }],
979                        down_context: vec![],
980                        start,
981                        end,
982                        flag: EdgeFlags::BLOCK,
983                        inode: Position {
984                            change: None,
985                            pos: inode_pos,
986                        },
987                    })),
988                    encoding,
989                )
990            } else {
991                (None, encoding)
992            }
993        } else {
994            (None, None)
995        };
996
997        let name_start = ChangePosition(contents.len().into());
998        let file_meta = FileMetadata {
999            metadata: meta,
1000            basename: item.basename.as_str(),
1001            encoding: encoding.clone(),
1002        };
1003        file_meta.write(&mut contents);
1004        let name_end = ChangePosition(contents.len().into());
1005        debug!("name start {:?} end {:?}", name_start, name_end);
1006        contents.push(0);
1007        self.actions.push(Hunk::FileAdd {
1008            add_name: Atom::NewVertex(NewVertex {
1009                up_context: vec![item_v_papa],
1010                down_context: vec![],
1011                start: name_start,
1012                end: name_end,
1013                flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1014                inode: item.v_papa,
1015            }),
1016            add_inode: Atom::NewVertex(NewVertex {
1017                up_context: vec![Position {
1018                    change: None,
1019                    pos: name_end,
1020                }],
1021                down_context: vec![],
1022                start: inode_pos,
1023                end: inode_pos,
1024                flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1025                inode: item.v_papa,
1026            }),
1027            contents: contents_,
1028            path: item.full_path.clone(),
1029            encoding,
1030        });
1031        debug!("{:?}", self.actions.last().unwrap());
1032        self.updatables.insert(
1033            self.actions.len(),
1034            InodeUpdate::Add {
1035                inode: item.inode,
1036                pos: inode_pos,
1037            },
1038        );
1039        if meta.is_dir() {
1040            Ok(Some(Position {
1041                change: None,
1042                pos: inode_pos,
1043            }))
1044        } else {
1045            Ok(None)
1046        }
1047    }
1048
1049    #[allow(clippy::too_many_arguments)]
1050    fn record_existing_file<T: ChannelTxnT + TreeTxnT, W: WorkingCopyRead + Clone, C: ChangeStore>(
1051        &mut self,
1052        txn: &ArcTxn<T>,
1053        diff_algorithm: diff::Algorithm,
1054        stop_early: bool,
1055        diff_sep: &regex::bytes::Regex,
1056        channel: &ChannelRef<T>,
1057        working_copy: W,
1058        changes: &C,
1059        item: &RecordItem,
1060        new_papa: Option<Position<Option<ChangeId>>>,
1061        vertex: Position<ChangeId>,
1062    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1063    where
1064        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1065    {
1066        debug!(
1067            "record_existing_file {:?}: {:?} {:?} {:?}",
1068            item.full_path, item.inode, vertex, new_papa,
1069        );
1070        // Former parent(s) of vertex
1071        let (former_parents, is_deleted, encoding) = {
1072            let txn_ = txn.read();
1073            let channel_ = channel.read();
1074            collect_former_parents::<C, W, T>(changes, &*txn_, &*channel_, vertex)?
1075        };
1076        debug!(
1077            "record_existing_file: {:?} {:?} {:?}",
1078            item, former_parents, is_deleted,
1079        );
1080        match working_copy.file_metadata(&item.full_path) {
1081            Ok(new_meta) => self.record_nondeleted(
1082                txn,
1083                diff_algorithm,
1084                stop_early,
1085                diff_sep,
1086                channel,
1087                working_copy,
1088                changes,
1089                item,
1090                new_papa,
1091                vertex,
1092                new_meta,
1093                &former_parents,
1094                is_deleted,
1095                encoding,
1096            )?,
1097            _ => {
1098                debug!("calling record_deleted_file on {:?}", item.full_path);
1099                let txn_ = txn.read();
1100                let channel_ = channel.read();
1101                self.record_deleted_file(
1102                    &*txn_,
1103                    txn_.graph(&*channel_),
1104                    &working_copy,
1105                    &item.full_path,
1106                    vertex,
1107                    changes,
1108                )?
1109            }
1110        }
1111        Ok(())
1112    }
1113
1114    #[allow(clippy::too_many_arguments)]
1115    fn record_nondeleted<T: ChannelTxnT + TreeTxnT, W: WorkingCopyRead + Clone, C: ChangeStore>(
1116        &mut self,
1117        txn: &ArcTxn<T>,
1118        diff_algorithm: diff::Algorithm,
1119        stop_early: bool,
1120        diff_sep: &regex::bytes::Regex,
1121        channel: &ChannelRef<T>,
1122        working_copy: W,
1123        changes: &C,
1124        item: &RecordItem,
1125        new_papa: Option<Position<Option<ChangeId>>>,
1126        vertex: Position<ChangeId>,
1127        new_meta: InodeMetadata,
1128        former_parents: &[Parent],
1129        is_deleted: bool,
1130        encoding: Option<Encoding>,
1131    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1132    where
1133        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1134    {
1135        if former_parents.is_empty() {
1136            // This is the case where the inode exists both in the
1137            // graph and in the inode tables, but isn't alive in the
1138            // graph.
1139            //
1140            // This can happen (1) when outputting a tag that has this
1141            // file, after recording the deletion of the file, or (2)
1142            // when recording after applying, but before outputting,
1143            // but this is a misuse of the library.
1144            debug!("new_papa = {:?}", new_papa);
1145            let txn = txn.read();
1146            let channel = channel.read();
1147            self.record_moved_file::<_, _, W>(
1148                changes,
1149                &*txn,
1150                &*channel,
1151                item,
1152                vertex,
1153                new_papa.unwrap(),
1154                encoding,
1155            )?
1156        } else if former_parents.len() > 1
1157            || former_parents[0].basename != item.basename
1158            || former_parents[0].metadata != item.metadata
1159            || former_parents[0].parent != item.v_papa
1160            || is_deleted
1161        {
1162            debug!("new_papa = {:?}", new_papa);
1163            let txn = txn.read();
1164            let channel = channel.read();
1165            self.record_moved_file::<_, _, W>(
1166                changes,
1167                &*txn,
1168                &*channel,
1169                item,
1170                vertex,
1171                new_papa.unwrap(),
1172                former_parents[0].encoding.clone(),
1173            )?
1174        }
1175        if new_meta.is_file()
1176            && (self.force_rediff
1177                || modified_since_last_commit(
1178                    &*txn.read(),
1179                    &*channel.read(),
1180                    &working_copy,
1181                    &item.full_path,
1182                )?)
1183        {
1184            let mut ret = {
1185                let txn = txn.read();
1186                let channel = channel.read();
1187                retrieve(&*txn, txn.graph(&*channel), vertex, false)?
1188            };
1189            let mut b = Vec::new();
1190            let encoding = working_copy
1191                .decode_file(&item.full_path, &mut b)
1192                .map_err(RecordError::WorkingCopy)?;
1193            debug!("diffing…");
1194            let len = self.actions.len();
1195            self.diff(
1196                changes,
1197                txn,
1198                channel,
1199                diff_algorithm,
1200                stop_early,
1201                item.full_path.clone(),
1202                item.inode,
1203                vertex.to_option(),
1204                &mut ret,
1205                &b,
1206                &encoding,
1207                diff_sep,
1208            )?;
1209            if self.actions.len() > len
1210                && let Ok(last_modified) = working_copy.modified_time(&item.full_path)
1211            {
1212                if self.oldest_change == std::time::SystemTime::UNIX_EPOCH {
1213                    self.oldest_change = last_modified;
1214                } else {
1215                    self.oldest_change = self.oldest_change.min(last_modified);
1216                }
1217            }
1218            debug!(
1219                "new actions: {:?}, total {:?}",
1220                self.actions.len() - len,
1221                self.actions.len()
1222            );
1223        }
1224        Ok(())
1225    }
1226
1227    #[allow(clippy::too_many_arguments)]
1228    fn record_moved_file<T: ChannelTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
1229        &mut self,
1230        changes: &C,
1231        txn: &T,
1232        channel: &T::Channel,
1233        item: &RecordItem,
1234        vertex: Position<ChangeId>,
1235        new_papa: Position<Option<ChangeId>>,
1236        encoding: Option<Encoding>,
1237    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1238    where
1239        <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1240    {
1241        debug!("record_moved_file {:?} {:?}", item, vertex);
1242        let basename = item.basename.as_str();
1243        let mut moved = collect_moved_edges::<_, _, W>(
1244            txn,
1245            changes,
1246            txn.graph(channel),
1247            new_papa,
1248            vertex,
1249            item.metadata,
1250            basename,
1251        )?;
1252        debug!("moved = {:#?}", moved);
1253        let is_resurrected = !moved.resurrect.is_empty();
1254        if is_resurrected {
1255            moved.resurrect.append(&mut moved.alive);
1256            if !moved.need_new_name {
1257                moved.resurrect.append(&mut moved.edges);
1258            }
1259            self.actions.push(Hunk::FileUndel {
1260                undel: Atom::EdgeMap(EdgeMap {
1261                    edges: moved.resurrect,
1262                    inode: item.v_papa,
1263                }),
1264                contents: None,
1265                path: item.full_path.clone(),
1266                encoding: encoding.clone(),
1267            });
1268        }
1269
1270        let item_v_papa = if !moved.edges.is_empty() && moved.need_new_name {
1271            self.add_root_if_needed(item.v_papa)
1272        } else {
1273            item.v_papa
1274        };
1275
1276        let mut contents = self.contents.lock();
1277        contents.push(0);
1278        let meta_start = ChangePosition(contents.len().into());
1279        FileMetadata {
1280            metadata: item.metadata,
1281            basename,
1282            encoding: encoding.clone(),
1283        }
1284        .write(&mut contents);
1285        let meta_end = ChangePosition(contents.len().into());
1286        contents.push(0);
1287        if !moved.edges.is_empty() {
1288            // If there was exactly one alive name, this is a regular
1289            // move, i.e. not a conflict.
1290            if moved.n_alive_names == 1 || (moved.need_new_name && !is_resurrected) {
1291                debug!("need_new_name {:?}", item.v_papa);
1292                let add = if moved.need_new_name && !is_resurrected {
1293                    moved.edges.append(&mut moved.alive);
1294                    Atom::NewVertex(NewVertex {
1295                        up_context: vec![item_v_papa],
1296                        down_context: vec![vertex.to_option()],
1297                        start: meta_start,
1298                        end: meta_end,
1299                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1300                        inode: item_v_papa,
1301                    })
1302                } else {
1303                    Atom::EdgeMap(EdgeMap {
1304                        edges: moved.alive,
1305                        inode: item_v_papa,
1306                    })
1307                };
1308                self.actions.push(Hunk::FileMove {
1309                    del: Atom::EdgeMap(EdgeMap {
1310                        edges: moved.edges,
1311                        inode: item.v_papa,
1312                    }),
1313                    add,
1314                    path: crate::fs::find_path(changes, txn, channel, true, vertex)?
1315                        .unwrap()
1316                        .path
1317                        .join("/"),
1318                });
1319            } else {
1320                self.actions.push(Hunk::SolveNameConflict {
1321                    name: Atom::EdgeMap(EdgeMap {
1322                        edges: moved.edges,
1323                        inode: item.v_papa,
1324                    }),
1325                    path: item.full_path.clone(),
1326                });
1327                contents.truncate(meta_start.0.as_usize())
1328            }
1329        } else {
1330            contents.truncate(meta_start.0.as_usize())
1331        }
1332        Ok(())
1333    }
1334
1335    pub fn take_updatables(&mut self) -> HashMap<usize, InodeUpdate> {
1336        std::mem::take(&mut self.updatables)
1337    }
1338
1339    pub fn into_change<T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>>(
1340        self,
1341        txn: &T,
1342        channel: &ChannelRef<T>,
1343        header: crate::change::ChangeHeader,
1344    ) -> Result<crate::change::LocalChangeLocal, crate::change::MakeChangeError<T>> {
1345        let actions = self
1346            .actions
1347            .into_iter()
1348            .map(|rec| rec.globalize(txn).unwrap())
1349            .collect();
1350        let contents = if let Ok(c) = Arc::try_unwrap(self.contents) {
1351            c.into_inner()
1352        } else {
1353            unreachable!()
1354        };
1355        crate::change::LocalChange::make_change(txn, channel, actions, contents, header, Vec::new())
1356    }
1357}
1358
1359#[allow(clippy::type_complexity)]
1360fn collect_former_parents<C: ChangeStore, W: WorkingCopyRead, T: ChannelTxnT + TreeTxnT>(
1361    changes: &C,
1362    txn: &T,
1363    channel: &T::Channel,
1364    vertex: Position<ChangeId>,
1365) -> Result<(Vec<Parent>, bool, Option<Encoding>), RecordError<C::Error, W::Error, T>>
1366where
1367    W::Error: 'static,
1368{
1369    let mut former_parents = Vec::new();
1370    let f0 = EdgeFlags::FOLDER | EdgeFlags::PARENT;
1371    let f1 = EdgeFlags::all();
1372    let mut is_deleted = true;
1373    let mut encoding_ = None;
1374    for name_ in iter_adjacent(txn, txn.graph(channel), vertex.inode_vertex(), f0, f1)? {
1375        debug!("name_ = {:?}", name_);
1376        let name_ = name_?;
1377        if !name_.flag().contains(EdgeFlags::PARENT) {
1378            debug!("continue");
1379            continue;
1380        }
1381
1382        let name_dest = txn
1383            .find_block_end(txn.graph(channel), name_.dest())
1384            .unwrap();
1385        let mut meta = vec![0; name_dest.end - name_dest.start];
1386        let FileMetadata {
1387            basename,
1388            metadata,
1389            encoding,
1390        } = changes
1391            .get_file_meta(
1392                |p| txn.get_external(&p).ok().map(From::from),
1393                *name_dest,
1394                &mut meta,
1395            )
1396            .map_err(RecordError::Changestore)?;
1397        debug!(
1398            "former basename of {:?}: {:?} {:?}",
1399            vertex, basename, metadata
1400        );
1401
1402        if name_.flag().contains(EdgeFlags::DELETED) {
1403            debug!("is_deleted {:?}", name_);
1404            is_deleted = true;
1405            if encoding_.is_none() {
1406                encoding_ = encoding
1407            }
1408            break;
1409        }
1410        if let Some(v_papa) = iter_adjacent(txn, txn.graph(channel), *name_dest, f0, f1)?.next() {
1411            let v_papa = v_papa?;
1412            if !v_papa.flag().contains(EdgeFlags::DELETED) {
1413                if encoding_.is_none() {
1414                    encoding_ = encoding.clone()
1415                }
1416                former_parents.push(Parent {
1417                    basename: basename.to_string(),
1418                    metadata,
1419                    encoding,
1420                    parent: v_papa.dest().to_option(),
1421                })
1422            }
1423        }
1424    }
1425    Ok((former_parents, is_deleted, encoding_))
1426}
1427
1428#[derive(Debug)]
1429struct MovedEdges {
1430    edges: Vec<NewEdge<Option<ChangeId>>>,
1431    alive: Vec<NewEdge<Option<ChangeId>>>,
1432    resurrect: Vec<NewEdge<Option<ChangeId>>>,
1433    need_new_name: bool,
1434    n_alive_names: usize,
1435}
1436
1437fn collect_moved_edges<T: GraphTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
1438    txn: &T,
1439    changes: &C,
1440    channel: &T::Graph,
1441    parent_pos: Position<Option<ChangeId>>,
1442    current_pos: Position<ChangeId>,
1443    new_meta: InodeMetadata,
1444    name: &str,
1445) -> Result<MovedEdges, RecordError<C::Error, W::Error, T>>
1446where
1447    <W as crate::working_copy::WorkingCopyRead>::Error: 'static,
1448{
1449    debug!("collect_moved_edges {:?}", current_pos);
1450    let mut moved = MovedEdges {
1451        edges: Vec::new(),
1452        alive: Vec::new(),
1453        resurrect: Vec::new(),
1454        need_new_name: true,
1455        n_alive_names: 0,
1456    };
1457    let mut del_del = HashMap::default();
1458    let mut alive = HashMap::default();
1459    let mut previous_name = Vec::new();
1460    let mut last_alive_meta = None;
1461    let mut is_first_parent = true;
1462    for parent in iter_adjacent(
1463        txn,
1464        channel,
1465        current_pos.inode_vertex(),
1466        EdgeFlags::FOLDER | EdgeFlags::PARENT,
1467        EdgeFlags::all(),
1468    )? {
1469        let parent = parent?;
1470        if !parent
1471            .flag()
1472            .contains(EdgeFlags::FOLDER | EdgeFlags::PARENT)
1473        {
1474            continue;
1475        }
1476        debug!("parent = {:?}", parent);
1477        let mut parent_was_resurrected = false;
1478        if !parent.flag().contains(EdgeFlags::PSEUDO) {
1479            if parent.flag().contains(EdgeFlags::DELETED) {
1480                debug!("resurrecting parent");
1481                moved.resurrect.push(NewEdge {
1482                    previous: parent.flag() - EdgeFlags::PARENT,
1483                    flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1484                    from: parent.dest().to_option(),
1485                    to: current_pos.inode_vertex().to_option(),
1486                    introduced_by: Some(parent.introduced_by()),
1487                });
1488                parent_was_resurrected = true;
1489                let v = alive
1490                    .entry((parent.dest(), current_pos.inode_vertex()))
1491                    .or_insert_with(HashSet::new);
1492                v.insert(None);
1493            } else {
1494                let v = alive
1495                    .entry((parent.dest(), current_pos.inode_vertex()))
1496                    .or_insert_with(HashSet::new);
1497                v.insert(Some(parent.introduced_by()));
1498            }
1499        }
1500        debug!("parent_was_resurrected: {:?}", parent_was_resurrected);
1501        let parent_dest = txn.find_block_end(channel, parent.dest()).unwrap();
1502        previous_name.resize(parent_dest.end - parent_dest.start, 0);
1503        let FileMetadata {
1504            metadata: parent_meta,
1505            basename: parent_name,
1506            ..
1507        } = changes
1508            .get_file_meta(
1509                |p| txn.get_external(&p).ok().map(From::from),
1510                *parent_dest,
1511                &mut previous_name,
1512            )
1513            .map_err(RecordError::Changestore)?;
1514        debug!(
1515            "parent_dest {:?} {:?} {:?} {:?}",
1516            parent_dest, parent_meta, parent_name, name
1517        );
1518        let name_changed = parent_name != name;
1519        let mut meta_changed = new_meta != parent_meta;
1520        if cfg!(windows)
1521            && !meta_changed
1522            && let Some(m) = last_alive_meta
1523        {
1524            meta_changed = new_meta != m
1525        }
1526        let mut name_is_alive = false;
1527        for grandparent in iter_adjacent(
1528            txn,
1529            channel,
1530            *parent_dest,
1531            EdgeFlags::FOLDER | EdgeFlags::PARENT,
1532            EdgeFlags::all(),
1533        )? {
1534            let grandparent = grandparent?;
1535            if !grandparent
1536                .flag()
1537                .contains(EdgeFlags::FOLDER | EdgeFlags::PARENT)
1538                || grandparent.flag().contains(EdgeFlags::PSEUDO)
1539            {
1540                continue;
1541            }
1542            debug!("grandparent: {:?}", grandparent);
1543            let grandparent_dest = txn.find_block_end(channel, grandparent.dest()).unwrap();
1544            assert_eq!(grandparent_dest.start, grandparent_dest.end);
1545            debug!(
1546                "grandparent_dest {:?} {:?}, parent_pos = {:?}",
1547                grandparent_dest,
1548                std::str::from_utf8(&previous_name[2..]),
1549                parent_pos,
1550            );
1551            let grandparent_changed = if parent_pos.change == Some(ChangeId::ROOT) {
1552                // Because repos may have multiple roots there may be
1553                // a mismatch here.  The "no change" case when
1554                // `parent_pos` is a root vertex is when
1555                // `grandparent.dest()` is also a root vertex.
1556                !is_root_vertex(txn, channel, grandparent.dest())?
1557            } else {
1558                parent_pos != grandparent.dest().to_option()
1559            };
1560            debug!(
1561                "change = {:?} {:?} {:?}",
1562                grandparent_changed, name_changed, meta_changed
1563            );
1564            if !grandparent.flag().contains(EdgeFlags::DELETED) {
1565                name_is_alive = true
1566            }
1567            if grandparent.flag().contains(EdgeFlags::DELETED) {
1568                if !grandparent_changed && !name_changed && !meta_changed {
1569                    // We resurrect the name
1570                    (if parent_was_resurrected {
1571                        &mut moved.resurrect
1572                    } else {
1573                        &mut moved.alive
1574                    })
1575                    .push(NewEdge {
1576                        previous: grandparent.flag() - EdgeFlags::PARENT,
1577                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1578                        from: grandparent.dest().to_option(),
1579                        to: parent_dest.to_option(),
1580                        introduced_by: Some(grandparent.introduced_by()),
1581                    });
1582                    if !parent_was_resurrected && !parent.flag().contains(EdgeFlags::PSEUDO) {
1583                        moved.alive.push(NewEdge {
1584                            previous: parent.flag() - EdgeFlags::PARENT,
1585                            flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1586                            from: parent.dest().to_option(),
1587                            to: current_pos.inode_vertex().to_option(),
1588                            introduced_by: Some(parent.introduced_by()),
1589                        })
1590                    }
1591                    moved.need_new_name = false;
1592                    // We've found an alive parent, delete the others.
1593                    is_first_parent = false;
1594                } else {
1595                    // Clean up the extra deleted edges.
1596                    debug!("cleanup");
1597                    let v = del_del
1598                        .entry((grandparent.dest(), parent_dest))
1599                        .or_insert_with(HashSet::new);
1600                    v.insert(Some(grandparent.introduced_by()));
1601                }
1602            } else if grandparent_changed
1603                || name_changed
1604                || (meta_changed && cfg!(unix))
1605                || !is_first_parent
1606            {
1607                if !parent.flag().contains(EdgeFlags::PSEUDO) {
1608                    moved.edges.push(NewEdge {
1609                        previous: parent.flag() - EdgeFlags::PARENT - EdgeFlags::PSEUDO,
1610                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1611                        from: grandparent.dest().to_option(),
1612                        to: parent_dest.to_option(),
1613                        introduced_by: Some(grandparent.introduced_by()),
1614                    });
1615                    // The following extra edge is meant to allow
1616                    // detection of missing contexts in folders: indeed,
1617                    // if we didn't have it, we couldn't tell the
1618                    // difference between a convergent renaming or
1619                    // deletion and a conflict between a renaming and a
1620                    // deletion.\
1621                    if !parent_was_resurrected {
1622                        moved.alive.push(NewEdge {
1623                            previous: parent.flag() - EdgeFlags::PARENT,
1624                            flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1625                            from: parent.dest().to_option(),
1626                            to: current_pos.inode_vertex().to_option(),
1627                            introduced_by: Some(parent.introduced_by()),
1628                        })
1629                    }
1630                }
1631            } else {
1632                last_alive_meta = Some(new_meta);
1633                let v = alive
1634                    .entry((grandparent.dest(), *parent_dest))
1635                    .or_insert_with(HashSet::new);
1636                v.insert(Some(grandparent.introduced_by()));
1637                moved.need_new_name = false;
1638                // We've found an alive parent, delete the others.
1639                is_first_parent = false;
1640            }
1641        }
1642        if name_is_alive {
1643            moved.n_alive_names += 1
1644        }
1645    }
1646
1647    for ((from, to), intro) in del_del {
1648        if intro.len() > 1 {
1649            for introduced_by in intro {
1650                if introduced_by.is_some() {
1651                    moved.edges.push(NewEdge {
1652                        previous: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1653                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1654                        from: from.to_option(),
1655                        to: to.to_option(),
1656                        introduced_by,
1657                    })
1658                }
1659            }
1660        }
1661    }
1662
1663    debug!("alive = {:#?}", alive);
1664
1665    for ((from, to), intro) in alive {
1666        if intro.len() > 1 || !moved.resurrect.is_empty() {
1667            for introduced_by in intro {
1668                if introduced_by.is_some() {
1669                    moved.alive.push(NewEdge {
1670                        previous: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1671                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1672                        from: from.to_option(),
1673                        to: to.to_option(),
1674                        introduced_by,
1675                    })
1676                }
1677            }
1678        }
1679    }
1680
1681    Ok(moved)
1682}
1683
1684/// Is this vertex a (potentially deleted) "root vertex", i.e. a root
1685/// of the file hierarchy?
1686fn is_root_vertex<T: GraphTxnT>(
1687    txn: &T,
1688    channel: &T::Graph,
1689    v: Position<ChangeId>,
1690) -> Result<bool, TxnErr<T::GraphError>> {
1691    for parent in iter_adjacent(
1692        txn,
1693        channel,
1694        v.inode_vertex(),
1695        EdgeFlags::FOLDER | EdgeFlags::PARENT,
1696        EdgeFlags::all(),
1697    )? {
1698        let parent = parent?;
1699        if !parent.flag().contains(EdgeFlags::PARENT) {
1700            continue;
1701        }
1702        let p = parent.dest();
1703        let p = txn.find_block_end(channel, p).unwrap();
1704        if p.start == p.end {
1705            return Ok(true);
1706        } else {
1707            return Ok(false);
1708        }
1709    }
1710    Ok(false)
1711}
1712
1713impl Recorded {
1714    fn record_deleted_file<T: GraphTxnT + TreeTxnT, W: WorkingCopyRead, C: ChangeStore>(
1715        &mut self,
1716        txn: &T,
1717        channel: &T::Graph,
1718        working_copy: &W,
1719        full_path: &str,
1720        current_vertex: Position<ChangeId>,
1721        changes: &C,
1722    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1723    where
1724        <W as WorkingCopyRead>::Error: 'static,
1725    {
1726        debug!("record_deleted_file {:?} {:?}", current_vertex, full_path);
1727        let mut stack = vec![(current_vertex.inode_vertex(), None)];
1728        let mut visited = HashSet::default();
1729        let mut full_path = std::borrow::Cow::Borrowed(full_path);
1730        while let Some((vertex, inode)) = stack.pop() {
1731            debug!("vertex {:?}, inode {:?}", vertex, inode);
1732            if let Some(path) = tree_path(txn, &vertex.start_pos())? {
1733                if working_copy.file_metadata(&path).is_ok() {
1734                    debug!("not deleting {:?}", path);
1735                    continue;
1736                }
1737                full_path = path.into()
1738            }
1739
1740            // Kill this vertex
1741            if let Some(inode) = inode {
1742                self.delete_file_edge(txn, channel, vertex, inode)?
1743            } else if vertex.start == vertex.end {
1744                debug!("delete_recursively {:?}", vertex);
1745                // Killing an inode.
1746                {
1747                    let mut deleted_vertices = self.deleted_vertices.lock();
1748                    if !deleted_vertices.insert(vertex.start_pos()) {
1749                        continue;
1750                    }
1751                }
1752                if let Some(inode) = txn.get_revinodes(&vertex.start_pos(), None)? {
1753                    debug!(
1754                        "delete_recursively, vertex = {:?}, inode = {:?}",
1755                        vertex, inode
1756                    );
1757                    self.recorded_inodes
1758                        .lock()
1759                        .insert(*inode, vertex.start_pos().to_option());
1760                    self.updatables.insert(
1761                        self.actions.len() + 1,
1762                        InodeUpdate::Deleted { inode: *inode },
1763                    );
1764                }
1765                self.delete_inode_vertex::<_, _, W>(
1766                    changes,
1767                    txn,
1768                    channel,
1769                    vertex,
1770                    vertex.start_pos(),
1771                    &full_path,
1772                )?
1773            }
1774
1775            // Move on to the descendants.
1776            for edge in iter_adjacent(
1777                txn,
1778                channel,
1779                vertex,
1780                EdgeFlags::empty(),
1781                EdgeFlags::all() - EdgeFlags::DELETED - EdgeFlags::PARENT,
1782            )? {
1783                let edge = edge?;
1784                debug!("delete_recursively, edge: {:?}", edge);
1785                let dest = txn
1786                    .find_block(channel, edge.dest())
1787                    .expect("delete_recursively, descendants");
1788                let inode = if inode.is_some() {
1789                    assert!(!edge.flag().contains(EdgeFlags::FOLDER));
1790                    inode
1791                } else if edge.flag().contains(EdgeFlags::FOLDER) {
1792                    None
1793                } else {
1794                    assert_eq!(vertex.start, vertex.end);
1795                    Some(vertex.start_pos())
1796                };
1797                if visited.insert(edge.dest()) {
1798                    stack.push((*dest, inode))
1799                }
1800            }
1801        }
1802        Ok(())
1803    }
1804
1805    fn delete_inode_vertex<T: GraphTxnT + TreeTxnT, C: ChangeStore, W: WorkingCopyRead>(
1806        &mut self,
1807        changes: &C,
1808        txn: &T,
1809        channel: &T::Graph,
1810        vertex: Vertex<ChangeId>,
1811        inode: Position<ChangeId>,
1812        path: &str,
1813    ) -> Result<(), RecordError<C::Error, W::Error, T>>
1814    where
1815        <W as WorkingCopyRead>::Error: 'static,
1816    {
1817        debug!("delete_inode_vertex {:?}", path);
1818        let mut edges = Vec::new();
1819        let mut enc = None;
1820        let mut previous_name = Vec::new();
1821        for parent in iter_adjacent(
1822            txn,
1823            channel,
1824            vertex,
1825            EdgeFlags::FOLDER | EdgeFlags::PARENT,
1826            EdgeFlags::all(),
1827        )? {
1828            let parent = parent?;
1829            if !parent.flag().contains(EdgeFlags::PARENT) {
1830                continue;
1831            }
1832            assert!(parent.flag().contains(EdgeFlags::FOLDER));
1833            let parent_dest = txn.find_block_end(channel, parent.dest()).unwrap();
1834            if enc.is_none() {
1835                previous_name.resize(parent_dest.end - parent_dest.start, 0);
1836                let FileMetadata { encoding, .. } = changes
1837                    .get_file_meta(
1838                        |p| txn.get_external(&p).ok().map(From::from),
1839                        *parent_dest,
1840                        &mut previous_name,
1841                    )
1842                    .map_err(RecordError::Changestore)?;
1843                enc = Some(encoding);
1844            }
1845
1846            for grandparent in iter_adjacent(
1847                txn,
1848                channel,
1849                *parent_dest,
1850                EdgeFlags::FOLDER | EdgeFlags::PARENT,
1851                EdgeFlags::all(),
1852            )? {
1853                let grandparent = grandparent?;
1854                if !grandparent.flag().contains(EdgeFlags::PARENT)
1855                    || grandparent.flag().contains(EdgeFlags::PSEUDO)
1856                {
1857                    continue;
1858                }
1859                assert!(grandparent.flag().contains(EdgeFlags::PARENT));
1860                assert!(grandparent.flag().contains(EdgeFlags::FOLDER));
1861                edges.push(NewEdge {
1862                    previous: grandparent.flag() - EdgeFlags::PARENT,
1863                    flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1864                    from: grandparent.dest().to_option(),
1865                    to: parent_dest.to_option(),
1866                    introduced_by: Some(grandparent.introduced_by()),
1867                });
1868            }
1869            if !parent.flag().contains(EdgeFlags::PSEUDO) {
1870                edges.push(NewEdge {
1871                    previous: parent.flag() - EdgeFlags::PARENT,
1872                    flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK | EdgeFlags::DELETED,
1873                    from: parent.dest().to_option(),
1874                    to: vertex.to_option(),
1875                    introduced_by: Some(parent.introduced_by()),
1876                });
1877            }
1878        }
1879        debug!("deleting {:?}", edges);
1880        if !edges.is_empty() {
1881            self.actions.push(Hunk::FileDel {
1882                del: Atom::EdgeMap(EdgeMap {
1883                    edges,
1884                    inode: inode.to_option(),
1885                }),
1886                contents: None,
1887                path: path.to_string(),
1888                encoding: enc.unwrap(),
1889            })
1890        }
1891        Ok(())
1892    }
1893
1894    fn delete_file_edge<T: GraphTxnT>(
1895        &mut self,
1896        txn: &T,
1897        channel: &T::Graph,
1898        to: Vertex<ChangeId>,
1899        inode: Position<ChangeId>,
1900    ) -> Result<(), TxnErr<T::GraphError>> {
1901        if let Some(Hunk::FileDel { contents, .. }) = self.actions.last_mut() {
1902            if contents.is_none() {
1903                *contents = Some(Atom::EdgeMap(EdgeMap {
1904                    edges: Vec::new(),
1905                    inode: inode.to_option(),
1906                }))
1907            }
1908            if let Some(Atom::EdgeMap(mut e)) = contents.take() {
1909                for parent in iter_adjacent(
1910                    txn,
1911                    channel,
1912                    to,
1913                    EdgeFlags::PARENT,
1914                    EdgeFlags::all() - EdgeFlags::DELETED,
1915                )? {
1916                    let parent = parent?;
1917                    if parent.flag().contains(EdgeFlags::PSEUDO) {
1918                        continue;
1919                    }
1920                    assert!(parent.flag().contains(EdgeFlags::PARENT));
1921                    assert!(!parent.flag().contains(EdgeFlags::FOLDER));
1922                    e.edges.push(NewEdge {
1923                        previous: parent.flag() - EdgeFlags::PARENT,
1924                        flag: (parent.flag() - EdgeFlags::PARENT) | EdgeFlags::DELETED,
1925                        from: parent.dest().to_option(),
1926                        to: to.to_option(),
1927                        introduced_by: Some(parent.introduced_by()),
1928                    })
1929                }
1930                if !e.edges.is_empty() {
1931                    *contents = Some(Atom::EdgeMap(e))
1932                }
1933            }
1934        } else {
1935            unreachable!()
1936        }
1937        Ok(())
1938    }
1939}