libpijul_compat/
record.rs

1use backend::*;
2use graph;
3use optimal_diff;
4use patch::*;
5use {ErrorKind, Result};
6
7use rand;
8use std;
9use std::cell::RefCell;
10use std::collections::HashSet;
11use std::fs::metadata;
12use std::io::BufRead;
13use std::io::Read;
14#[cfg(not(windows))]
15use std::os::unix::fs::PermissionsExt;
16use std::path::{Path, PathBuf};
17use std::rc::Rc;
18
19#[cfg(not(windows))]
20fn permissions(attr: &std::fs::Metadata) -> Option<usize> {
21    Some(attr.permissions().mode() as usize)
22}
23#[cfg(windows)]
24fn permissions(_: &std::fs::Metadata) -> Option<usize> {
25    None
26}
27
28fn file_metadata(path: &Path) -> Result<FileMetadata> {
29    let attr = metadata(&path)?;
30    let permissions = permissions(&attr).unwrap_or(0o755);
31    debug!("permissions = {:?}", permissions);
32    Ok(FileMetadata::new(permissions & 0o777, attr.is_dir()))
33}
34
35impl<U: Transaction, R> GenericTxn<U, R> {
36    pub fn globalize_change(
37        &self,
38        change: Change<Rc<RefCell<ChangeContext<PatchId>>>>,
39    ) -> Change<ChangeContext<Hash>> {
40        match change {
41            Change::NewNodes {
42                up_context,
43                down_context,
44                flag,
45                line_num,
46                nodes,
47                inode,
48            } => Change::NewNodes {
49                up_context: Rc::try_unwrap(up_context)
50                    .unwrap()
51                    .into_inner()
52                    .iter()
53                    .map(|&k| self.external_key_opt(k))
54                    .collect(),
55                down_context: Rc::try_unwrap(down_context)
56                    .unwrap()
57                    .into_inner()
58                    .iter()
59                    .map(|&k| self.external_key_opt(k))
60                    .collect(),
61                flag,
62                line_num,
63                nodes,
64                inode,
65            },
66            Change::NewEdges {
67                previous,
68                flag,
69                edges,
70                inode,
71            } => Change::NewEdges {
72                previous,
73                flag,
74                edges,
75                inode,
76            },
77        }
78    }
79    pub fn globalize_record(
80        &self,
81        change: Record<Rc<RefCell<ChangeContext<PatchId>>>>,
82    ) -> Record<ChangeContext<Hash>> {
83        match change {
84            Record::FileMove { new_name, del, add } => Record::FileMove {
85                new_name,
86                del: self.globalize_change(del),
87                add: self.globalize_change(add),
88            },
89            Record::FileDel { name, del } => Record::FileDel {
90                name,
91                del: self.globalize_change(del),
92            },
93            Record::FileAdd { name, add } => Record::FileAdd {
94                name,
95                add: self.globalize_change(add),
96            },
97            Record::Change {
98                file,
99                change,
100                conflict_reordering,
101            } => Record::Change {
102                file,
103                change: self.globalize_change(change),
104                conflict_reordering: conflict_reordering
105                    .into_iter()
106                    .map(|x| self.globalize_change(x))
107                    .collect(),
108            },
109            Record::Replace {
110                file,
111                adds,
112                dels,
113                conflict_reordering,
114            } => Record::Replace {
115                file,
116                adds: self.globalize_change(adds),
117                dels: self.globalize_change(dels),
118                conflict_reordering: conflict_reordering
119                    .into_iter()
120                    .map(|x| self.globalize_change(x))
121                    .collect(),
122            },
123        }
124    }
125}
126
127pub struct RecordState {
128    line_num: LineId,
129    updatables: HashSet<InodeUpdate>,
130    actions: Vec<Record<Rc<RefCell<ChangeContext<PatchId>>>>>,
131    redundant: Vec<(Key<PatchId>, Edge)>,
132}
133
134/// An account of the files that have been added, moved or deleted, as
135/// returned by record, and used by apply (when applying a patch
136/// created locally) to update the trees and inodes databases.
137#[derive(Debug, Hash, PartialEq, Eq)]
138pub enum InodeUpdate {
139    Add {
140        /// `LineId` in the new patch.
141        line: LineId,
142        /// `FileMetadata` in the updated file.
143        meta: FileMetadata,
144        /// `Inode` added by this file addition.
145        inode: Inode,
146    },
147    Moved {
148        /// `Inode` of the moved file.
149        inode: Inode,
150    },
151    Deleted {
152        /// `Inode` of the deleted file.
153        inode: Inode,
154    },
155}
156
157#[derive(Debug)]
158pub enum WorkingFileStatus {
159    Moved {
160        from: FileMetadata,
161        to: FileMetadata,
162    },
163    Deleted,
164    Ok,
165    Zombie,
166}
167
168fn is_text(x: &[u8]) -> bool {
169    x.iter().take(8000).all(|&c| c != 0)
170}
171
172impl<'env, R: rand::Rng> MutTxn<'env, R> {
173    /// Create appropriate NewNodes for adding a file.
174    fn record_file_addition(
175        &self,
176        st: &mut RecordState,
177        current_inode: Inode,
178        parent_node: Key<Option<PatchId>>,
179        realpath: &mut std::path::PathBuf,
180        basename: &str,
181    ) -> Result<Option<LineId>> {
182        let name_line_num = st.line_num.clone();
183        let blank_line_num = st.line_num + 1;
184        st.line_num += 2;
185
186        debug!("metadata for {:?}", realpath);
187        let meta = match file_metadata(&realpath) {
188            Ok(metadata) => metadata,
189            Err(e) => return Err(e),
190        };
191
192        let mut name = Vec::with_capacity(basename.len() + 2);
193        name.write_metadata(meta).unwrap(); // 2 bytes.
194        name.extend(basename.as_bytes());
195
196        let mut nodes = Vec::new();
197
198        st.updatables.insert(InodeUpdate::Add {
199            line: blank_line_num.clone(),
200            meta: meta,
201            inode: current_inode.clone(),
202        });
203        let up_context_ext = Key {
204            patch: if parent_node.line.is_root() {
205                Some(Hash::None)
206            } else if let Some(patch_id) = parent_node.patch {
207                Some(self.external_hash(patch_id).to_owned())
208            } else {
209                None
210            },
211            line: parent_node.line.clone(),
212        };
213        let up_context = Key {
214            patch: if parent_node.line.is_root() {
215                Some(ROOT_PATCH_ID)
216            } else if let Some(patch_id) = parent_node.patch {
217                Some(patch_id)
218            } else {
219                None
220            },
221            line: parent_node.line.clone(),
222        };
223        st.actions.push(Record::FileAdd {
224            name: realpath.to_string_lossy().to_string(),
225            add: Change::NewNodes {
226                up_context: Rc::new(RefCell::new(vec![up_context])),
227                line_num: name_line_num,
228                down_context: Rc::new(RefCell::new(vec![])),
229                nodes: vec![name, vec![]],
230                flag: EdgeFlags::FOLDER_EDGE,
231                inode: up_context_ext.clone(),
232            },
233        });
234        // Reading the file
235        if !meta.is_dir() {
236            nodes.clear();
237
238            let mut node = Vec::new();
239            {
240                let mut f = std::fs::File::open(realpath.as_path())?;
241                f.read_to_end(&mut node)?;
242            }
243
244            let up_context = Key {
245                patch: None,
246                line: blank_line_num.clone(),
247            };
248            if is_text(&node) {
249                let mut line = Vec::new();
250                let mut f = &node[..];
251                loop {
252                    match f.read_until('\n' as u8, &mut line) {
253                        Ok(l) => {
254                            if l > 0 {
255                                nodes.push(line.clone());
256                                line.clear()
257                            } else {
258                                break;
259                            }
260                        }
261                        Err(_) => break,
262                    }
263                }
264                let len = nodes.len();
265                if !nodes.is_empty() {
266                    st.actions.push(Record::Change {
267                        change: Change::NewNodes {
268                            up_context: Rc::new(RefCell::new(vec![up_context])),
269                            line_num: st.line_num,
270                            down_context: Rc::new(RefCell::new(vec![])),
271                            nodes: nodes,
272                            flag: EdgeFlags::empty(),
273                            inode: up_context_ext.clone(),
274                        },
275                        file: Rc::new(realpath.clone()),
276                        conflict_reordering: Vec::new(),
277                    });
278                }
279                st.line_num += len;
280            } else {
281                st.actions.push(Record::Change {
282                    change: Change::NewNodes {
283                        up_context: Rc::new(RefCell::new(vec![up_context])),
284                        line_num: st.line_num,
285                        down_context: Rc::new(RefCell::new(vec![])),
286                        nodes: vec![node],
287                        flag: EdgeFlags::empty(),
288                        inode: up_context_ext.clone(),
289                    },
290                    file: Rc::new(realpath.clone()),
291                    conflict_reordering: Vec::new(),
292                });
293                st.line_num += 1;
294            }
295            Ok(None)
296        } else {
297            Ok(Some(blank_line_num))
298        }
299    }
300
301    /// Diff for binary files, doesn't both splitting the file in
302    /// lines. This is wasteful, but doesn't break the format, and
303    /// doesn't create conflicts inside binary files.
304    fn diff_with_binary(
305        &self,
306        inode: Key<Option<Hash>>,
307        branch: &Branch,
308        st: &mut RecordState,
309        ret: &mut graph::Graph,
310        path: Rc<PathBuf>,
311    ) -> Result<()> {
312        let mut lines_b = Vec::new();
313        {
314            debug!("opening file for diff: {:?}", path);
315            let mut f = std::fs::File::open(path.as_ref())?;
316            f.read_to_end(&mut lines_b)?;
317        }
318        let lines = if is_text(&lines_b) {
319            optimal_diff::read_lines(&lines_b)
320        } else {
321            vec![&lines_b[..]]
322        };
323
324        self.diff(
325            inode,
326            branch,
327            path,
328            &mut st.line_num,
329            &mut st.actions,
330            &mut st.redundant,
331            ret,
332            &lines,
333        )
334    }
335
336    fn record_moved_file(
337        &self,
338        branch: &Branch,
339        realpath: &mut std::path::PathBuf,
340        st: &mut RecordState,
341        parent_node: Key<Option<PatchId>>,
342        current_node: Key<PatchId>,
343        basename: &str,
344        new_meta: FileMetadata,
345        old_meta: FileMetadata,
346    ) -> Result<()> {
347        debug!("record_moved_file: parent_node={:?}", parent_node);
348        // Delete all former names.
349        let mut edges = Vec::new();
350        // Now take all grandparents of l2, delete them.
351
352        let mut name = Vec::with_capacity(basename.len() + 2);
353        name.write_metadata(new_meta).unwrap();
354        name.extend(basename.as_bytes());
355        for parent in self.iter_parents(branch, current_node, EdgeFlags::FOLDER_EDGE) {
356            debug!("iter_parents: {:?}", parent);
357            let previous_name: &[u8] = match self.get_contents(parent.dest) {
358                None => &[],
359                Some(n) => n.as_slice(),
360            };
361            let name_changed =
362                (&previous_name[2..] != &name[2..]) || (new_meta != old_meta && cfg!(not(windows)));
363
364            for grandparent in self.iter_parents(branch, parent.dest, EdgeFlags::FOLDER_EDGE) {
365                debug!("iter_parents: grandparent = {:?}", grandparent);
366                let grandparent_changed = if let Some(ref parent_node_patch) = parent_node.patch {
367                    *parent_node_patch != grandparent.dest.patch
368                        || parent_node.line != grandparent.dest.line
369                } else {
370                    true
371                };
372                if grandparent_changed || name_changed {
373                    edges.push(NewEdge {
374                        from: Key {
375                            line: parent.dest.line.clone(),
376                            patch: Some(self.external_hash(parent.dest.patch).to_owned()),
377                        },
378                        to: Key {
379                            line: grandparent.dest.line.clone(),
380                            patch: Some(self.external_hash(grandparent.dest.patch).to_owned()),
381                        },
382                        introduced_by: Some(
383                            self.external_hash(grandparent.introduced_by).to_owned(),
384                        ),
385                    })
386                }
387            }
388        }
389        debug!("edges:{:?}", edges);
390        let up_context_ext = Key {
391            patch: if parent_node.line.is_root() {
392                Some(Hash::None)
393            } else if let Some(parent_patch) = parent_node.patch {
394                Some(self.external_hash(parent_patch).to_owned())
395            } else {
396                None
397            },
398            line: parent_node.line.clone(),
399        };
400        let up_context = Key {
401            patch: if parent_node.line.is_root() {
402                Some(ROOT_PATCH_ID)
403            } else if let Some(parent_patch) = parent_node.patch {
404                Some(parent_patch)
405            } else {
406                None
407            },
408            line: parent_node.line.clone(),
409        };
410        if !edges.is_empty() {
411            // If this file's name or meta info has changed.
412            st.actions.push(Record::FileMove {
413                new_name: realpath.to_string_lossy().to_string(),
414                del: Change::NewEdges {
415                    edges: edges,
416                    previous: EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE,
417                    flag: EdgeFlags::DELETED_EDGE | EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE,
418                    inode: up_context_ext.clone(),
419                },
420                add: Change::NewNodes {
421                    up_context: Rc::new(RefCell::new(vec![up_context])),
422                    line_num: st.line_num,
423                    down_context: Rc::new(RefCell::new(vec![Key {
424                        patch: Some(current_node.patch),
425                        line: current_node.line.clone(),
426                    }])),
427                    nodes: vec![name],
428                    flag: EdgeFlags::FOLDER_EDGE,
429                    inode: up_context_ext.clone(),
430                },
431            });
432            st.line_num += 1;
433        }
434        // debug!("directory_flag:{}", old_attr & DIRECTORY_FLAG);
435        if !old_meta.is_dir() {
436            info!("retrieving");
437            let mut ret = self.retrieve(branch, current_node);
438            debug!("diff");
439            self.diff_with_binary(
440                up_context_ext,
441                branch,
442                st,
443                &mut ret,
444                Rc::new(realpath.clone()),
445            )?;
446        };
447        Ok(())
448    }
449
450    fn record_deleted_file(
451        &self,
452        st: &mut RecordState,
453        branch: &Branch,
454        realpath: &Path,
455        current_node: Key<PatchId>,
456    ) -> Result<()> {
457        debug!("record_deleted_file");
458        let mut edges = Vec::new();
459        let mut previous = EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE;
460        // Now take all grandparents of the current node, delete them.
461        for parent in self.iter_parents(branch, current_node, EdgeFlags::FOLDER_EDGE) {
462            for grandparent in self.iter_parents(branch, parent.dest, EdgeFlags::FOLDER_EDGE) {
463                edges.push(NewEdge {
464                    from: self.external_key(&parent.dest).unwrap(),
465                    to: self.external_key(&grandparent.dest).unwrap(),
466                    introduced_by: Some(self.external_hash(grandparent.introduced_by).to_owned()),
467                });
468                previous = grandparent.flag;
469            }
470        }
471        // Delete the file recursively
472        let mut file_edges = vec![];
473        {
474            debug!("del={:?}", current_node);
475            let ret = self.retrieve(branch, current_node);
476            debug!("ret {:?}", ret);
477            for l in ret.lines.iter() {
478                if l.key != ROOT_KEY {
479                    let ext_key = self.external_key(&l.key).unwrap();
480                    debug!("ext_key={:?}", ext_key);
481                    for v in self.iter_parents(branch, l.key, EdgeFlags::empty()) {
482                        debug!("v={:?}", v);
483                        file_edges.push(NewEdge {
484                            from: ext_key.clone(),
485                            to: self.external_key(&v.dest).unwrap(),
486                            introduced_by: Some(self.external_hash(v.introduced_by).to_owned()),
487                        });
488                        if let Some(inode) = self.get_revinodes(v.dest) {
489                            st.updatables.insert(InodeUpdate::Deleted {
490                                inode: inode.to_owned(),
491                            });
492                        }
493                    }
494                    for v in self.iter_parents(branch, l.key, EdgeFlags::FOLDER_EDGE) {
495                        debug!("v={:?}", v);
496                        edges.push(NewEdge {
497                            from: ext_key.clone(),
498                            to: self.external_key(&v.dest).unwrap(),
499                            introduced_by: Some(self.external_hash(v.introduced_by).to_owned()),
500                        });
501                    }
502                }
503            }
504        }
505
506        if !edges.is_empty() {
507            st.actions.push(Record::FileDel {
508                name: realpath.to_string_lossy().to_string(),
509                del: Change::NewEdges {
510                    edges: edges,
511                    previous,
512                    flag: EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE | EdgeFlags::DELETED_EDGE,
513                    inode: self.external_key(&current_node).unwrap(),
514                },
515            });
516        }
517        if !file_edges.is_empty() {
518            st.actions.push(Record::Change {
519                change: Change::NewEdges {
520                    edges: file_edges,
521                    previous: EdgeFlags::PARENT_EDGE,
522                    flag: EdgeFlags::PARENT_EDGE | EdgeFlags::DELETED_EDGE,
523                    inode: self.external_key(&current_node).unwrap(),
524                },
525                file: Rc::new(realpath.to_path_buf()),
526                conflict_reordering: Vec::new(),
527            });
528        }
529        Ok(())
530    }
531
532    fn record_children(
533        &self,
534        branch: &Branch,
535        st: &mut RecordState,
536        path: &mut std::path::PathBuf,
537        current_node: Key<Option<PatchId>>,
538        current_inode: Inode,
539        obsolete_inodes: &mut Vec<Inode>,
540    ) -> Result<()> {
541        debug!("children of current_inode {}", current_inode.to_hex());
542        let file_id = OwnedFileId {
543            parent_inode: current_inode.clone(),
544            basename: SmallString::from_str(""),
545        };
546        debug!("iterating tree, starting from {:?}", file_id.as_file_id());
547        for (k, v) in self.iter_tree(Some((&file_id.as_file_id(), None)))
548            .take_while(|&(ref k, _)| k.parent_inode == current_inode)
549        {
550            debug!("calling record_all recursively, {}", line!());
551
552            if k.basename.len() > 0 {
553                // If this is an actual file and not just the "."
554                self.record_inode(
555                    branch,
556                    st,
557                    current_node.clone(), // parent
558                    v,                    // current_inode
559                    path,
560                    obsolete_inodes,
561                    k.basename.as_str(),
562                )?
563            }
564        }
565        Ok(())
566    }
567
568    /// If `inode` is a file known to the current branch, return
569    /// whether it's been moved, deleted, or its "status" (including
570    /// permissions) has been changed.
571    ///
572    /// Returns `None` if `inode` is not known to the current branch.
573    fn inode_status(&self, inode: Inode, path: &Path) -> (Option<(WorkingFileStatus, FileHeader)>) {
574        match self.get_inodes(inode) {
575            Some(file_header) => {
576                let old_meta = file_header.metadata;
577                let new_meta = file_metadata(path).ok();
578
579                debug!("current_node={:?}", file_header);
580                debug!("old_attr={:?},int_attr={:?}", old_meta, new_meta);
581
582                let status = match (new_meta, file_header.status) {
583                    (Some(new_meta), FileStatus::Moved) => WorkingFileStatus::Moved {
584                        from: old_meta,
585                        to: new_meta,
586                    },
587                    (Some(new_meta), _) if old_meta != new_meta => WorkingFileStatus::Moved {
588                        from: old_meta,
589                        to: new_meta,
590                    },
591                    (None, _) | (_, FileStatus::Deleted) => WorkingFileStatus::Deleted,
592                    (Some(_), FileStatus::Ok) => WorkingFileStatus::Ok,
593                    (Some(_), FileStatus::Zombie) => WorkingFileStatus::Zombie,
594                };
595                Some((status, file_header.clone()))
596            }
597            None => None,
598        }
599    }
600
601    fn record_inode(
602        &self,
603        branch: &Branch,
604        st: &mut RecordState,
605        parent_node: Key<Option<PatchId>>,
606        current_inode: Inode,
607        realpath: &mut std::path::PathBuf,
608        obsolete_inodes: &mut Vec<Inode>,
609        basename: &str,
610    ) -> Result<()> {
611        realpath.push(basename);
612        debug!("realpath: {:?}", realpath);
613        debug!("inode: {:?}", current_inode);
614        debug!("header: {:?}", self.get_inodes(current_inode));
615        let status_header = self.inode_status(current_inode, realpath);
616        debug!("status_header: {:?}", status_header);
617        let mut current_key = match &status_header {
618            &Some((_, ref file_header)) => Some(Key {
619                patch: Some(file_header.key.patch.clone()),
620                line: file_header.key.line.clone(),
621            }),
622            &None => None,
623        };
624
625        match status_header {
626            Some((
627                WorkingFileStatus::Moved {
628                    from: old_meta,
629                    to: new_meta,
630                },
631                file_header,
632            )) => {
633                st.updatables.insert(InodeUpdate::Moved {
634                    inode: current_inode.clone(),
635                });
636                self.record_moved_file(
637                    branch,
638                    realpath,
639                    st,
640                    parent_node,
641                    file_header.key,
642                    basename,
643                    new_meta,
644                    old_meta,
645                )?
646            }
647            Some((WorkingFileStatus::Deleted, file_header)) => {
648                st.updatables.insert(InodeUpdate::Deleted {
649                    inode: current_inode.clone(),
650                });
651                self.record_deleted_file(st, branch, realpath, file_header.key)?
652            }
653            Some((WorkingFileStatus::Ok, file_header)) => {
654                if !file_header.metadata.is_dir() {
655                    let mut ret = self.retrieve(branch, file_header.key);
656                    debug!("now calling diff {:?}", file_header.key);
657                    let inode = Key {
658                        patch: if file_header.key.line.is_root() {
659                            Some(Hash::None)
660                        } else if let Some(patch_id) = parent_node.patch {
661                            Some(self.external_hash(patch_id).to_owned())
662                        } else {
663                            None
664                        },
665                        line: parent_node.line.clone(),
666                    };
667                    self.confirm_path(st, branch, &realpath, file_header.key)?;
668                    self.diff_with_binary(inode, branch, st, &mut ret, Rc::new(realpath.clone()))?;
669                } else {
670                    // Confirm
671                    self.confirm_path(st, branch, &realpath, file_header.key)?;
672                }
673            }
674            Some((WorkingFileStatus::Zombie, _)) => {
675                // This file is a zombie, but the user has not
676                // specified anything to do with this file, so leave
677                // it alone.
678            }
679            None => {
680                if let Ok(new_key) =
681                    self.record_file_addition(st, current_inode, parent_node, realpath, basename)
682                {
683                    current_key = new_key.map(|next| Key {
684                        patch: None,
685                        line: next,
686                    })
687                } else {
688                    obsolete_inodes.push(current_inode)
689                }
690            }
691        }
692
693        let current_key = current_key;
694        debug!("current_node={:?}", current_key);
695        if let Some(current_node) = current_key {
696            self.record_children(
697                branch,
698                st,
699                realpath,
700                current_node,
701                current_inode,
702                obsolete_inodes,
703            )?;
704        };
705        realpath.pop();
706        Ok(())
707    }
708
709    fn external_newedge(
710        &self,
711        from: Key<PatchId>,
712        to: Key<PatchId>,
713        introduced_by: PatchId,
714    ) -> NewEdge {
715        NewEdge {
716            from: Key {
717                patch: Some(self.external_hash(from.patch).to_owned()),
718                line: from.line,
719            },
720            to: Key {
721                patch: Some(self.external_hash(to.patch).to_owned()),
722                line: to.line,
723            },
724            introduced_by: Some(self.external_hash(introduced_by).to_owned()),
725        }
726    }
727
728    /// `key` must be a non-root inode key.
729    fn confirm_path(
730        &self,
731        st: &mut RecordState,
732        branch: &Branch,
733        realpath: &Path,
734        key: Key<PatchId>,
735    ) -> Result<()> {
736        debug!("confirm_path");
737        let e =
738            Edge::zero(EdgeFlags::PARENT_EDGE | EdgeFlags::FOLDER_EDGE | EdgeFlags::DELETED_EDGE);
739        // Are there deleted parent edges?
740        let mut edges = Vec::new();
741        for (_, v) in self.iter_nodes(branch, Some((key, Some(&e))))
742            .filter(|&(k, v)| k == key && v.flag == e.flag)
743        {
744            debug!("confirm {:?}", v.dest);
745            edges.push(self.external_newedge(key, v.dest, v.introduced_by));
746            for (_, v_) in self.iter_nodes(branch, Some((v.dest, Some(&e))))
747                .filter(|&(k, v_)| k == v.dest && v_.flag == e.flag)
748            {
749                debug!("confirm 2 {:?}", v_.dest);
750                edges.push(self.external_newedge(v.dest, v_.dest, v_.introduced_by));
751            }
752        }
753
754        if !edges.is_empty() {
755            let inode = Key {
756                patch: Some(self.external_hash(key.patch).to_owned()),
757                line: key.line.clone(),
758            };
759            st.actions.push(Record::FileAdd {
760                name: realpath.to_string_lossy().to_string(),
761                add: Change::NewEdges {
762                    edges,
763                    previous: EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE
764                        | EdgeFlags::DELETED_EDGE,
765                    flag: EdgeFlags::FOLDER_EDGE | EdgeFlags::PARENT_EDGE,
766                    inode,
767                },
768            });
769        }
770        debug!("/confirm_path");
771
772        Ok(())
773    }
774}
775
776impl RecordState {
777    pub fn new() -> Self {
778        RecordState {
779            line_num: LineId::new() + 1,
780            actions: Vec::new(),
781            updatables: HashSet::new(),
782            redundant: Vec::new(),
783        }
784    }
785
786    pub fn finish(
787        self,
788    ) -> (
789        Vec<Record<Rc<RefCell<ChangeContext<PatchId>>>>>,
790        HashSet<InodeUpdate>,
791    ) {
792        (self.actions, self.updatables)
793    }
794}
795
796impl<'env, T: rand::Rng> MutTxn<'env, T> {
797    pub fn record(
798        &mut self,
799        state: &mut RecordState,
800        branch: &Branch,
801        working_copy: &std::path::Path,
802        prefix: Option<&std::path::Path>,
803    ) -> Result<()> {
804        let mut obsolete_inodes = Vec::new();
805        {
806            let mut realpath = PathBuf::from(working_copy);
807
808            if let Some(prefix) = prefix {
809                realpath.extend(prefix);
810                let basename = realpath.file_name().unwrap().to_str().unwrap().to_string();
811                realpath.pop();
812                let inode = self.find_inode(prefix)?;
813                // Key needs to be the parent's node.
814                let key: Key<PatchId> = {
815                    // find this inode's parent.
816                    if let Some(parent) = self.get_revtree(inode) {
817                        if parent.parent_inode.is_root() {
818                            ROOT_KEY
819                        } else if let Some(key) = self.get_inodes(parent.parent_inode) {
820                            key.key
821                        } else {
822                            return Err(ErrorKind::FileNotInRepo(prefix.to_path_buf()).into());
823                        }
824                    } else {
825                        return Err(ErrorKind::FileNotInRepo(prefix.to_path_buf()).into());
826                    }
827                };
828                let key = Key {
829                    patch: Some(key.patch),
830                    line: key.line,
831                };
832                self.record_inode(
833                    &branch,
834                    state,
835                    key,
836                    inode,
837                    &mut realpath,
838                    &mut obsolete_inodes,
839                    &basename,
840                )?
841            } else {
842                let key = Key {
843                    patch: None,
844                    line: LineId::new(),
845                };
846                self.record_children(
847                    &branch,
848                    state,
849                    &mut realpath,
850                    key,
851                    ROOT_INODE,
852                    &mut obsolete_inodes,
853                )?
854                // self.record_root(&branch, &mut st, &mut realpath)?;
855            }
856            debug!("record done, {} changes", state.actions.len());
857            debug!("changes: {:?}", state.actions);
858        }
859        // try!(self.remove_redundant_edges(&mut branch, &mut st.redundant));
860        debug!("remove_redundant_edges done");
861        for inode in obsolete_inodes {
862            self.rec_delete(inode)?;
863        }
864        Ok(())
865    }
866}