1use 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
66pub 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
88pub struct Recorded {
90 pub contents: Arc<Mutex<Vec<u8>>>,
92 pub actions: Vec<Hunk<Option<ChangeId>, LocalByte>>,
94 pub updatables: HashMap<usize, InodeUpdate>,
97 pub largest_file: u64,
99 pub has_binary_files: bool,
101 pub oldest_change: std::time::SystemTime,
104 pub redundant: Vec<crate::alive::Redundant>,
106 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 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 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#[derive(Debug, Hash, PartialEq, Eq)]
199pub enum InodeUpdate {
200 Add {
201 pos: ChangePosition,
203 inode: Inode,
205 },
206 Deleted {
207 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#[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: ®ex::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 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 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 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 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 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 self.recorded_inodes.lock().insert(item.inode, vertex);
416 vertex
417 }
418 _ => continue,
419 }
420 };
421
422 if root_vertices.is_empty() {
423 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 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: ®ex::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 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 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 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 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 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 self.recorded_inodes.lock().insert(item.inode, vertex);
636 vertex
637 }
638 _ => continue,
639 }
640 };
641
642 if root_vertices.is_empty() {
643 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 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 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 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 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: ®ex::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 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: ®ex::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 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 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 !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 (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 is_first_parent = false;
1594 } else {
1595 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 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 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
1684fn 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 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 {
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 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}