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#[derive(Debug, Hash, PartialEq, Eq)]
138pub enum InodeUpdate {
139 Add {
140 line: LineId,
142 meta: FileMetadata,
144 inode: Inode,
146 },
147 Moved {
148 inode: Inode,
150 },
151 Deleted {
152 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 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(); 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 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 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 let mut edges = Vec::new();
350 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 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 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 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 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(¤t_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(¤t_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 self.record_inode(
555 branch,
556 st,
557 current_node.clone(), v, path,
560 obsolete_inodes,
561 k.basename.as_str(),
562 )?
563 }
564 }
565 Ok(())
566 }
567
568 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 self.confirm_path(st, branch, &realpath, file_header.key)?;
672 }
673 }
674 Some((WorkingFileStatus::Zombie, _)) => {
675 }
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 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 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 let key: Key<PatchId> = {
815 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 }
856 debug!("record done, {} changes", state.actions.len());
857 debug!("changes: {:?}", state.actions);
858 }
859 debug!("remove_redundant_edges done");
861 for inode in obsolete_inodes {
862 self.rec_delete(inode)?;
863 }
864 Ok(())
865 }
866}