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