598 WithBasename::inclusive_ancestors_of(path); |
639 WithBasename::inclusive_ancestors_of(path); |
599 let mut ancestor_path = inclusive_ancestor_paths |
640 let mut ancestor_path = inclusive_ancestor_paths |
600 .next() |
641 .next() |
601 .expect("expected at least one inclusive ancestor"); |
642 .expect("expected at least one inclusive ancestor"); |
602 loop { |
643 loop { |
603 // TODO: can we avoid allocating an owned key in cases where the |
644 let (_, child_node) = child_nodes |
604 // map already contains that key, without introducing double |
|
605 // lookup? |
|
606 let child_node = child_nodes |
|
607 .make_mut(on_disk, unreachable_bytes)? |
645 .make_mut(on_disk, unreachable_bytes)? |
608 .entry(to_cow(ancestor_path)) |
646 .raw_entry_mut() |
609 .or_default(); |
647 .from_key(ancestor_path.base_name()) |
|
648 .or_insert_with(|| (to_cow(ancestor_path), Node::default())); |
610 if let Some(next) = inclusive_ancestor_paths.next() { |
649 if let Some(next) = inclusive_ancestor_paths.next() { |
611 each_ancestor(child_node); |
650 each_ancestor(child_node); |
612 ancestor_path = next; |
651 ancestor_path = next; |
613 child_nodes = &mut child_node.children; |
652 child_nodes = &mut child_node.children; |
614 } else { |
653 } else { |
615 return Ok(child_node); |
654 return Ok(child_node); |
616 } |
655 } |
617 } |
656 } |
618 } |
657 } |
619 |
658 |
620 fn add_or_remove_file( |
659 fn reset_state( |
|
660 &mut self, |
|
661 filename: &HgPath, |
|
662 old_entry_opt: Option<DirstateEntry>, |
|
663 wc_tracked: bool, |
|
664 p1_tracked: bool, |
|
665 p2_info: bool, |
|
666 has_meaningful_mtime: bool, |
|
667 parent_file_data_opt: Option<ParentFileData>, |
|
668 ) -> Result<(), DirstateError> { |
|
669 let (had_entry, was_tracked) = match old_entry_opt { |
|
670 Some(old_entry) => (true, old_entry.tracked()), |
|
671 None => (false, false), |
|
672 }; |
|
673 let node = self.get_or_insert_node(filename, |ancestor| { |
|
674 if !had_entry { |
|
675 ancestor.descendants_with_entry_count += 1; |
|
676 } |
|
677 if was_tracked { |
|
678 if !wc_tracked { |
|
679 ancestor.tracked_descendants_count = ancestor |
|
680 .tracked_descendants_count |
|
681 .checked_sub(1) |
|
682 .expect("tracked count to be >= 0"); |
|
683 } |
|
684 } else { |
|
685 if wc_tracked { |
|
686 ancestor.tracked_descendants_count += 1; |
|
687 } |
|
688 } |
|
689 })?; |
|
690 |
|
691 let v2_data = if let Some(parent_file_data) = parent_file_data_opt { |
|
692 DirstateV2Data { |
|
693 wc_tracked, |
|
694 p1_tracked, |
|
695 p2_info, |
|
696 mode_size: parent_file_data.mode_size, |
|
697 mtime: if has_meaningful_mtime { |
|
698 parent_file_data.mtime |
|
699 } else { |
|
700 None |
|
701 }, |
|
702 ..Default::default() |
|
703 } |
|
704 } else { |
|
705 DirstateV2Data { |
|
706 wc_tracked, |
|
707 p1_tracked, |
|
708 p2_info, |
|
709 ..Default::default() |
|
710 } |
|
711 }; |
|
712 node.data = NodeData::Entry(DirstateEntry::from_v2_data(v2_data)); |
|
713 if !had_entry { |
|
714 self.nodes_with_entry_count += 1; |
|
715 } |
|
716 Ok(()) |
|
717 } |
|
718 |
|
719 fn set_tracked( |
|
720 &mut self, |
|
721 filename: &HgPath, |
|
722 old_entry_opt: Option<DirstateEntry>, |
|
723 ) -> Result<bool, DirstateV2ParseError> { |
|
724 let was_tracked = old_entry_opt.map_or(false, |e| e.tracked()); |
|
725 let had_entry = old_entry_opt.is_some(); |
|
726 let tracked_count_increment = if was_tracked { 0 } else { 1 }; |
|
727 let mut new = false; |
|
728 |
|
729 let node = self.get_or_insert_node(filename, |ancestor| { |
|
730 if !had_entry { |
|
731 ancestor.descendants_with_entry_count += 1; |
|
732 } |
|
733 |
|
734 ancestor.tracked_descendants_count += tracked_count_increment; |
|
735 })?; |
|
736 if let Some(old_entry) = old_entry_opt { |
|
737 let mut e = old_entry.clone(); |
|
738 if e.tracked() { |
|
739 // XXX |
|
740 // This is probably overkill for more case, but we need this to |
|
741 // fully replace the `normallookup` call with `set_tracked` |
|
742 // one. Consider smoothing this in the future. |
|
743 e.set_possibly_dirty(); |
|
744 } else { |
|
745 new = true; |
|
746 e.set_tracked(); |
|
747 } |
|
748 node.data = NodeData::Entry(e) |
|
749 } else { |
|
750 node.data = NodeData::Entry(DirstateEntry::new_tracked()); |
|
751 self.nodes_with_entry_count += 1; |
|
752 new = true; |
|
753 }; |
|
754 Ok(new) |
|
755 } |
|
756 |
|
757 /// Set a node as untracked in the dirstate. |
|
758 /// |
|
759 /// It is the responsibility of the caller to remove the copy source and/or |
|
760 /// the entry itself if appropriate. |
|
761 /// |
|
762 /// # Panics |
|
763 /// |
|
764 /// Panics if the node does not exist. |
|
765 fn set_untracked( |
|
766 &mut self, |
|
767 filename: &HgPath, |
|
768 old_entry: DirstateEntry, |
|
769 ) -> Result<(), DirstateV2ParseError> { |
|
770 let node = self |
|
771 .get_node_mut(filename, |ancestor| { |
|
772 ancestor.tracked_descendants_count = ancestor |
|
773 .tracked_descendants_count |
|
774 .checked_sub(1) |
|
775 .expect("tracked_descendants_count should be >= 0"); |
|
776 })? |
|
777 .expect("node should exist"); |
|
778 let mut new_entry = old_entry.clone(); |
|
779 new_entry.set_untracked(); |
|
780 node.data = NodeData::Entry(new_entry); |
|
781 Ok(()) |
|
782 } |
|
783 |
|
784 /// Set a node as clean in the dirstate. |
|
785 /// |
|
786 /// It is the responsibility of the caller to remove the copy source. |
|
787 /// |
|
788 /// # Panics |
|
789 /// |
|
790 /// Panics if the node does not exist. |
|
791 fn set_clean( |
|
792 &mut self, |
|
793 filename: &HgPath, |
|
794 old_entry: DirstateEntry, |
|
795 mode: u32, |
|
796 size: u32, |
|
797 mtime: TruncatedTimestamp, |
|
798 ) -> Result<(), DirstateError> { |
|
799 let node = self |
|
800 .get_node_mut(filename, |ancestor| { |
|
801 if !old_entry.tracked() { |
|
802 ancestor.tracked_descendants_count += 1; |
|
803 } |
|
804 })? |
|
805 .expect("node should exist"); |
|
806 let mut new_entry = old_entry.clone(); |
|
807 new_entry.set_clean(mode, size, mtime); |
|
808 node.data = NodeData::Entry(new_entry); |
|
809 Ok(()) |
|
810 } |
|
811 |
|
812 /// Set a node as possibly dirty in the dirstate. |
|
813 /// |
|
814 /// # Panics |
|
815 /// |
|
816 /// Panics if the node does not exist. |
|
817 fn set_possibly_dirty( |
|
818 &mut self, |
|
819 filename: &HgPath, |
|
820 ) -> Result<(), DirstateError> { |
|
821 let node = self |
|
822 .get_node_mut(filename, |_ancestor| {})? |
|
823 .expect("node should exist"); |
|
824 let entry = node.data.as_entry_mut().expect("entry should exist"); |
|
825 entry.set_possibly_dirty(); |
|
826 node.data = NodeData::Entry(*entry); |
|
827 Ok(()) |
|
828 } |
|
829 |
|
830 /// Clears the cached mtime for the (potential) folder at `path`. |
|
831 pub(super) fn clear_cached_mtime( |
621 &mut self, |
832 &mut self, |
622 path: &HgPath, |
833 path: &HgPath, |
623 old_state: Option<EntryState>, |
|
624 new_entry: DirstateEntry, |
|
625 ) -> Result<(), DirstateV2ParseError> { |
834 ) -> Result<(), DirstateV2ParseError> { |
626 let had_entry = old_state.is_some(); |
835 let node = match self.get_node_mut(path, |_ancestor| {})? { |
627 let was_tracked = old_state.map_or(false, |s| s.is_tracked()); |
836 Some(node) => node, |
628 let tracked_count_increment = |
837 None => return Ok(()), |
629 match (was_tracked, new_entry.state().is_tracked()) { |
838 }; |
630 (false, true) => 1, |
839 if let NodeData::CachedDirectory { .. } = &node.data { |
631 (true, false) => -1, |
840 node.data = NodeData::None |
632 _ => 0, |
841 } |
633 }; |
842 Ok(()) |
634 |
843 } |
635 let node = Self::get_or_insert_node( |
844 |
636 self.on_disk, |
845 /// Sets the cached mtime for the (potential) folder at `path`. |
637 &mut self.unreachable_bytes, |
846 pub(super) fn set_cached_mtime( |
638 &mut self.root, |
847 &mut self, |
639 path, |
848 path: &HgPath, |
640 WithBasename::to_cow_owned, |
849 mtime: TruncatedTimestamp, |
641 |ancestor| { |
850 ) -> Result<(), DirstateV2ParseError> { |
642 if !had_entry { |
851 let node = match self.get_node_mut(path, |_ancestor| {})? { |
643 ancestor.descendants_with_entry_count += 1; |
852 Some(node) => node, |
644 } |
853 None => return Ok(()), |
645 |
854 }; |
646 // We can’t use `+= increment` because the counter is unsigned, |
855 match &node.data { |
647 // and we want debug builds to detect accidental underflow |
856 NodeData::Entry(_) => {} // Don’t overwrite an entry |
648 // through zero |
857 NodeData::CachedDirectory { .. } | NodeData::None => { |
649 match tracked_count_increment { |
858 node.data = NodeData::CachedDirectory { mtime } |
650 1 => ancestor.tracked_descendants_count += 1, |
859 } |
651 -1 => ancestor.tracked_descendants_count -= 1, |
860 } |
652 _ => {} |
|
653 } |
|
654 }, |
|
655 )?; |
|
656 if !had_entry { |
|
657 self.nodes_with_entry_count += 1 |
|
658 } |
|
659 node.data = NodeData::Entry(new_entry); |
|
660 Ok(()) |
861 Ok(()) |
661 } |
862 } |
662 |
863 |
663 fn iter_nodes<'tree>( |
864 fn iter_nodes<'tree>( |
664 &'tree self, |
865 &'tree self, |
745 map.nodes_with_entry_count = 0; |
946 map.nodes_with_entry_count = 0; |
746 map.nodes_with_copy_source_count = 0; |
947 map.nodes_with_copy_source_count = 0; |
747 }); |
948 }); |
748 } |
949 } |
749 |
950 |
750 pub fn set_entry( |
951 pub fn set_tracked( |
751 &mut self, |
952 &mut self, |
752 filename: &HgPath, |
953 filename: &HgPath, |
753 entry: DirstateEntry, |
954 ) -> Result<bool, DirstateV2ParseError> { |
754 ) -> Result<(), DirstateV2ParseError> { |
955 let old_entry_opt = self.get(filename)?; |
|
956 self.with_dmap_mut(|map| map.set_tracked(filename, old_entry_opt)) |
|
957 } |
|
958 |
|
959 pub fn set_untracked( |
|
960 &mut self, |
|
961 filename: &HgPath, |
|
962 ) -> Result<bool, DirstateError> { |
|
963 let old_entry_opt = self.get(filename)?; |
|
964 match old_entry_opt { |
|
965 None => Ok(false), |
|
966 Some(old_entry) => { |
|
967 if !old_entry.tracked() { |
|
968 // `DirstateMap::set_untracked` is not a noop if |
|
969 // already not tracked as it will decrement the |
|
970 // tracked counters while going down. |
|
971 return Ok(true); |
|
972 } |
|
973 if old_entry.added() { |
|
974 // Untracking an "added" entry will just result in a |
|
975 // worthless entry (and other parts of the code will |
|
976 // complain about it), just drop it entirely. |
|
977 self.drop_entry_and_copy_source(filename)?; |
|
978 return Ok(true); |
|
979 } |
|
980 if !old_entry.p2_info() { |
|
981 self.copy_map_remove(filename)?; |
|
982 } |
|
983 |
|
984 self.with_dmap_mut(|map| { |
|
985 map.set_untracked(filename, old_entry)?; |
|
986 Ok(true) |
|
987 }) |
|
988 } |
|
989 } |
|
990 } |
|
991 |
|
992 pub fn set_clean( |
|
993 &mut self, |
|
994 filename: &HgPath, |
|
995 mode: u32, |
|
996 size: u32, |
|
997 mtime: TruncatedTimestamp, |
|
998 ) -> Result<(), DirstateError> { |
|
999 let old_entry = match self.get(filename)? { |
|
1000 None => { |
|
1001 return Err( |
|
1002 DirstateMapError::PathNotFound(filename.into()).into() |
|
1003 ) |
|
1004 } |
|
1005 Some(e) => e, |
|
1006 }; |
|
1007 self.copy_map_remove(filename)?; |
755 self.with_dmap_mut(|map| { |
1008 self.with_dmap_mut(|map| { |
756 map.get_or_insert(&filename)?.data = NodeData::Entry(entry); |
1009 map.set_clean(filename, old_entry, mode, size, mtime) |
757 Ok(()) |
|
758 }) |
1010 }) |
759 } |
1011 } |
760 |
1012 |
761 pub fn add_file( |
1013 pub fn set_possibly_dirty( |
762 &mut self, |
|
763 filename: &HgPath, |
|
764 entry: DirstateEntry, |
|
765 ) -> Result<(), DirstateError> { |
|
766 let old_state = self.get(filename)?.map(|e| e.state()); |
|
767 self.with_dmap_mut(|map| { |
|
768 Ok(map.add_or_remove_file(filename, old_state, entry)?) |
|
769 }) |
|
770 } |
|
771 |
|
772 pub fn remove_file( |
|
773 &mut self, |
|
774 filename: &HgPath, |
|
775 in_merge: bool, |
|
776 ) -> Result<(), DirstateError> { |
|
777 let old_entry_opt = self.get(filename)?; |
|
778 let old_state = old_entry_opt.map(|e| e.state()); |
|
779 let mut size = 0; |
|
780 if in_merge { |
|
781 // XXX we should not be able to have 'm' state and 'FROM_P2' if not |
|
782 // during a merge. So I (marmoute) am not sure we need the |
|
783 // conditionnal at all. Adding double checking this with assert |
|
784 // would be nice. |
|
785 if let Some(old_entry) = old_entry_opt { |
|
786 // backup the previous state |
|
787 if old_entry.state() == EntryState::Merged { |
|
788 size = SIZE_NON_NORMAL; |
|
789 } else if old_entry.state() == EntryState::Normal |
|
790 && old_entry.size() == SIZE_FROM_OTHER_PARENT |
|
791 { |
|
792 // other parent |
|
793 size = SIZE_FROM_OTHER_PARENT; |
|
794 } |
|
795 } |
|
796 } |
|
797 if size == 0 { |
|
798 self.copy_map_remove(filename)?; |
|
799 } |
|
800 self.with_dmap_mut(|map| { |
|
801 let entry = DirstateEntry::new_removed(size); |
|
802 Ok(map.add_or_remove_file(filename, old_state, entry)?) |
|
803 }) |
|
804 } |
|
805 |
|
806 pub fn drop_entry_and_copy_source( |
|
807 &mut self, |
1014 &mut self, |
808 filename: &HgPath, |
1015 filename: &HgPath, |
809 ) -> Result<(), DirstateError> { |
1016 ) -> Result<(), DirstateError> { |
810 let was_tracked = self |
1017 if self.get(filename)?.is_none() { |
811 .get(filename)? |
1018 return Err(DirstateMapError::PathNotFound(filename.into()).into()); |
812 .map_or(false, |e| e.state().is_tracked()); |
1019 } |
|
1020 self.with_dmap_mut(|map| map.set_possibly_dirty(filename)) |
|
1021 } |
|
1022 |
|
1023 pub fn reset_state( |
|
1024 &mut self, |
|
1025 filename: &HgPath, |
|
1026 wc_tracked: bool, |
|
1027 p1_tracked: bool, |
|
1028 p2_info: bool, |
|
1029 has_meaningful_mtime: bool, |
|
1030 parent_file_data_opt: Option<ParentFileData>, |
|
1031 ) -> Result<(), DirstateError> { |
|
1032 if !(p1_tracked || p2_info || wc_tracked) { |
|
1033 self.drop_entry_and_copy_source(filename)?; |
|
1034 return Ok(()); |
|
1035 } |
|
1036 self.copy_map_remove(filename)?; |
|
1037 let old_entry_opt = self.get(filename)?; |
|
1038 self.with_dmap_mut(|map| { |
|
1039 map.reset_state( |
|
1040 filename, |
|
1041 old_entry_opt, |
|
1042 wc_tracked, |
|
1043 p1_tracked, |
|
1044 p2_info, |
|
1045 has_meaningful_mtime, |
|
1046 parent_file_data_opt, |
|
1047 ) |
|
1048 }) |
|
1049 } |
|
1050 |
|
1051 pub fn drop_entry_and_copy_source( |
|
1052 &mut self, |
|
1053 filename: &HgPath, |
|
1054 ) -> Result<(), DirstateError> { |
|
1055 let was_tracked = self.get(filename)?.map_or(false, |e| e.tracked()); |
813 struct Dropped { |
1056 struct Dropped { |
814 was_tracked: bool, |
1057 was_tracked: bool, |
815 had_entry: bool, |
1058 had_entry: bool, |
816 had_copy_source: bool, |
1059 had_copy_source: bool, |
817 } |
1060 } |
1209 }; |
1488 }; |
1210 Ok(Some((node.full_path(map.on_disk)?, debug_tuple))) |
1489 Ok(Some((node.full_path(map.on_disk)?, debug_tuple))) |
1211 })) |
1490 })) |
1212 } |
1491 } |
1213 } |
1492 } |
|
1493 #[cfg(test)] |
|
1494 mod tests { |
|
1495 use super::*; |
|
1496 |
|
1497 /// Shortcut to return tracked descendants of a path. |
|
1498 /// Panics if the path does not exist. |
|
1499 fn tracked_descendants(map: &OwningDirstateMap, path: &[u8]) -> u32 { |
|
1500 let path = dbg!(HgPath::new(path)); |
|
1501 let node = map.get_map().get_node(path); |
|
1502 node.unwrap().unwrap().tracked_descendants_count() |
|
1503 } |
|
1504 |
|
1505 /// Shortcut to return descendants with an entry. |
|
1506 /// Panics if the path does not exist. |
|
1507 fn descendants_with_an_entry(map: &OwningDirstateMap, path: &[u8]) -> u32 { |
|
1508 let path = dbg!(HgPath::new(path)); |
|
1509 let node = map.get_map().get_node(path); |
|
1510 node.unwrap().unwrap().descendants_with_entry_count() |
|
1511 } |
|
1512 |
|
1513 fn assert_does_not_exist(map: &OwningDirstateMap, path: &[u8]) { |
|
1514 let path = dbg!(HgPath::new(path)); |
|
1515 let node = map.get_map().get_node(path); |
|
1516 assert!(node.unwrap().is_none()); |
|
1517 } |
|
1518 |
|
1519 /// Shortcut for path creation in tests |
|
1520 fn p(b: &[u8]) -> &HgPath { |
|
1521 HgPath::new(b) |
|
1522 } |
|
1523 |
|
1524 /// Test the very simple case a single tracked file |
|
1525 #[test] |
|
1526 fn test_tracked_descendants_simple() -> Result<(), DirstateError> { |
|
1527 let mut map = OwningDirstateMap::new_empty(vec![]); |
|
1528 assert_eq!(map.len(), 0); |
|
1529 |
|
1530 map.set_tracked(p(b"some/nested/path"))?; |
|
1531 |
|
1532 assert_eq!(map.len(), 1); |
|
1533 assert_eq!(tracked_descendants(&map, b"some"), 1); |
|
1534 assert_eq!(tracked_descendants(&map, b"some/nested"), 1); |
|
1535 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0); |
|
1536 |
|
1537 map.set_untracked(p(b"some/nested/path"))?; |
|
1538 assert_eq!(map.len(), 0); |
|
1539 assert!(map.get_map().get_node(p(b"some"))?.is_none()); |
|
1540 |
|
1541 Ok(()) |
|
1542 } |
|
1543 |
|
1544 /// Test the simple case of all tracked, but multiple files |
|
1545 #[test] |
|
1546 fn test_tracked_descendants_multiple() -> Result<(), DirstateError> { |
|
1547 let mut map = OwningDirstateMap::new_empty(vec![]); |
|
1548 |
|
1549 map.set_tracked(p(b"some/nested/path"))?; |
|
1550 map.set_tracked(p(b"some/nested/file"))?; |
|
1551 // one layer without any files to test deletion cascade |
|
1552 map.set_tracked(p(b"some/other/nested/path"))?; |
|
1553 map.set_tracked(p(b"root_file"))?; |
|
1554 map.set_tracked(p(b"some/file"))?; |
|
1555 map.set_tracked(p(b"some/file2"))?; |
|
1556 map.set_tracked(p(b"some/file3"))?; |
|
1557 |
|
1558 assert_eq!(map.len(), 7); |
|
1559 assert_eq!(tracked_descendants(&map, b"some"), 6); |
|
1560 assert_eq!(tracked_descendants(&map, b"some/nested"), 2); |
|
1561 assert_eq!(tracked_descendants(&map, b"some/other"), 1); |
|
1562 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); |
|
1563 assert_eq!(tracked_descendants(&map, b"some/nested/path"), 0); |
|
1564 |
|
1565 map.set_untracked(p(b"some/nested/path"))?; |
|
1566 assert_eq!(map.len(), 6); |
|
1567 assert_eq!(tracked_descendants(&map, b"some"), 5); |
|
1568 assert_eq!(tracked_descendants(&map, b"some/nested"), 1); |
|
1569 assert_eq!(tracked_descendants(&map, b"some/other"), 1); |
|
1570 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); |
|
1571 |
|
1572 map.set_untracked(p(b"some/nested/file"))?; |
|
1573 assert_eq!(map.len(), 5); |
|
1574 assert_eq!(tracked_descendants(&map, b"some"), 4); |
|
1575 assert_eq!(tracked_descendants(&map, b"some/other"), 1); |
|
1576 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); |
|
1577 assert_does_not_exist(&map, b"some_nested"); |
|
1578 |
|
1579 map.set_untracked(p(b"some/other/nested/path"))?; |
|
1580 assert_eq!(map.len(), 4); |
|
1581 assert_eq!(tracked_descendants(&map, b"some"), 3); |
|
1582 assert_does_not_exist(&map, b"some/other"); |
|
1583 |
|
1584 map.set_untracked(p(b"root_file"))?; |
|
1585 assert_eq!(map.len(), 3); |
|
1586 assert_eq!(tracked_descendants(&map, b"some"), 3); |
|
1587 assert_does_not_exist(&map, b"root_file"); |
|
1588 |
|
1589 map.set_untracked(p(b"some/file"))?; |
|
1590 assert_eq!(map.len(), 2); |
|
1591 assert_eq!(tracked_descendants(&map, b"some"), 2); |
|
1592 assert_does_not_exist(&map, b"some/file"); |
|
1593 |
|
1594 map.set_untracked(p(b"some/file2"))?; |
|
1595 assert_eq!(map.len(), 1); |
|
1596 assert_eq!(tracked_descendants(&map, b"some"), 1); |
|
1597 assert_does_not_exist(&map, b"some/file2"); |
|
1598 |
|
1599 map.set_untracked(p(b"some/file3"))?; |
|
1600 assert_eq!(map.len(), 0); |
|
1601 assert_does_not_exist(&map, b"some/file3"); |
|
1602 |
|
1603 Ok(()) |
|
1604 } |
|
1605 |
|
1606 /// Check with a mix of tracked and non-tracked items |
|
1607 #[test] |
|
1608 fn test_tracked_descendants_different() -> Result<(), DirstateError> { |
|
1609 let mut map = OwningDirstateMap::new_empty(vec![]); |
|
1610 |
|
1611 // A file that was just added |
|
1612 map.set_tracked(p(b"some/nested/path"))?; |
|
1613 // This has no information, the dirstate should ignore it |
|
1614 map.reset_state(p(b"some/file"), false, false, false, false, None)?; |
|
1615 assert_does_not_exist(&map, b"some/file"); |
|
1616 |
|
1617 // A file that was removed |
|
1618 map.reset_state( |
|
1619 p(b"some/nested/file"), |
|
1620 false, |
|
1621 true, |
|
1622 false, |
|
1623 false, |
|
1624 None, |
|
1625 )?; |
|
1626 assert!(!map.get(p(b"some/nested/file"))?.unwrap().tracked()); |
|
1627 // Only present in p2 |
|
1628 map.reset_state(p(b"some/file3"), false, false, true, false, None)?; |
|
1629 assert!(!map.get(p(b"some/file3"))?.unwrap().tracked()); |
|
1630 // A file that was merged |
|
1631 map.reset_state(p(b"root_file"), true, true, true, false, None)?; |
|
1632 assert!(map.get(p(b"root_file"))?.unwrap().tracked()); |
|
1633 // A file that is added, with info from p2 |
|
1634 // XXX is that actually possible? |
|
1635 map.reset_state(p(b"some/file2"), true, false, true, false, None)?; |
|
1636 assert!(map.get(p(b"some/file2"))?.unwrap().tracked()); |
|
1637 // A clean file |
|
1638 // One layer without any files to test deletion cascade |
|
1639 map.reset_state( |
|
1640 p(b"some/other/nested/path"), |
|
1641 true, |
|
1642 true, |
|
1643 false, |
|
1644 false, |
|
1645 None, |
|
1646 )?; |
|
1647 assert!(map.get(p(b"some/other/nested/path"))?.unwrap().tracked()); |
|
1648 |
|
1649 assert_eq!(map.len(), 6); |
|
1650 assert_eq!(tracked_descendants(&map, b"some"), 3); |
|
1651 assert_eq!(descendants_with_an_entry(&map, b"some"), 5); |
|
1652 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); |
|
1653 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1); |
|
1654 assert_eq!(tracked_descendants(&map, b"some/other/nested/path"), 0); |
|
1655 assert_eq!( |
|
1656 descendants_with_an_entry(&map, b"some/other/nested/path"), |
|
1657 0 |
|
1658 ); |
|
1659 assert_eq!(tracked_descendants(&map, b"some/nested"), 1); |
|
1660 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2); |
|
1661 |
|
1662 // might as well check this |
|
1663 map.set_untracked(p(b"path/does/not/exist"))?; |
|
1664 assert_eq!(map.len(), 6); |
|
1665 |
|
1666 map.set_untracked(p(b"some/other/nested/path"))?; |
|
1667 // It is set untracked but not deleted since it held other information |
|
1668 assert_eq!(map.len(), 6); |
|
1669 assert_eq!(tracked_descendants(&map, b"some"), 2); |
|
1670 assert_eq!(descendants_with_an_entry(&map, b"some"), 5); |
|
1671 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1); |
|
1672 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1); |
|
1673 assert_eq!(tracked_descendants(&map, b"some/nested"), 1); |
|
1674 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2); |
|
1675 |
|
1676 map.set_untracked(p(b"some/nested/path"))?; |
|
1677 // It is set untracked *and* deleted since it was only added |
|
1678 assert_eq!(map.len(), 5); |
|
1679 assert_eq!(tracked_descendants(&map, b"some"), 1); |
|
1680 assert_eq!(descendants_with_an_entry(&map, b"some"), 4); |
|
1681 assert_eq!(tracked_descendants(&map, b"some/nested"), 0); |
|
1682 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 1); |
|
1683 assert_does_not_exist(&map, b"some/nested/path"); |
|
1684 |
|
1685 map.set_untracked(p(b"root_file"))?; |
|
1686 // Untracked but not deleted |
|
1687 assert_eq!(map.len(), 5); |
|
1688 assert!(map.get(p(b"root_file"))?.is_some()); |
|
1689 |
|
1690 map.set_untracked(p(b"some/file2"))?; |
|
1691 assert_eq!(map.len(), 5); |
|
1692 assert_eq!(tracked_descendants(&map, b"some"), 0); |
|
1693 assert!(map.get(p(b"some/file2"))?.is_some()); |
|
1694 |
|
1695 map.set_untracked(p(b"some/file3"))?; |
|
1696 assert_eq!(map.len(), 5); |
|
1697 assert_eq!(tracked_descendants(&map, b"some"), 0); |
|
1698 assert!(map.get(p(b"some/file3"))?.is_some()); |
|
1699 |
|
1700 Ok(()) |
|
1701 } |
|
1702 |
|
1703 /// Check that copies counter is correctly updated |
|
1704 #[test] |
|
1705 fn test_copy_source() -> Result<(), DirstateError> { |
|
1706 let mut map = OwningDirstateMap::new_empty(vec![]); |
|
1707 |
|
1708 // Clean file |
|
1709 map.reset_state(p(b"files/clean"), true, true, false, false, None)?; |
|
1710 // Merged file |
|
1711 map.reset_state(p(b"files/from_p2"), true, true, true, false, None)?; |
|
1712 // Removed file |
|
1713 map.reset_state(p(b"removed"), false, true, false, false, None)?; |
|
1714 // Added file |
|
1715 map.reset_state(p(b"files/added"), true, false, false, false, None)?; |
|
1716 // Add copy |
|
1717 map.copy_map_insert(p(b"files/clean"), p(b"clean_copy_source"))?; |
|
1718 assert_eq!(map.copy_map_len(), 1); |
|
1719 |
|
1720 // Copy override |
|
1721 map.copy_map_insert(p(b"files/clean"), p(b"other_clean_copy_source"))?; |
|
1722 assert_eq!(map.copy_map_len(), 1); |
|
1723 |
|
1724 // Multiple copies |
|
1725 map.copy_map_insert(p(b"removed"), p(b"removed_copy_source"))?; |
|
1726 assert_eq!(map.copy_map_len(), 2); |
|
1727 |
|
1728 map.copy_map_insert(p(b"files/added"), p(b"added_copy_source"))?; |
|
1729 assert_eq!(map.copy_map_len(), 3); |
|
1730 |
|
1731 // Added, so the entry is completely removed |
|
1732 map.set_untracked(p(b"files/added"))?; |
|
1733 assert_does_not_exist(&map, b"files/added"); |
|
1734 assert_eq!(map.copy_map_len(), 2); |
|
1735 |
|
1736 // Removed, so the entry is kept around, so is its copy |
|
1737 map.set_untracked(p(b"removed"))?; |
|
1738 assert!(map.get(p(b"removed"))?.is_some()); |
|
1739 assert_eq!(map.copy_map_len(), 2); |
|
1740 |
|
1741 // Clean, so the entry is kept around, but not its copy |
|
1742 map.set_untracked(p(b"files/clean"))?; |
|
1743 assert!(map.get(p(b"files/clean"))?.is_some()); |
|
1744 assert_eq!(map.copy_map_len(), 1); |
|
1745 |
|
1746 map.copy_map_insert(p(b"files/from_p2"), p(b"from_p2_copy_source"))?; |
|
1747 assert_eq!(map.copy_map_len(), 2); |
|
1748 |
|
1749 // Info from p2, so its copy source info is kept around |
|
1750 map.set_untracked(p(b"files/from_p2"))?; |
|
1751 assert!(map.get(p(b"files/from_p2"))?.is_some()); |
|
1752 assert_eq!(map.copy_map_len(), 2); |
|
1753 |
|
1754 Ok(()) |
|
1755 } |
|
1756 |
|
1757 /// Test with "on disk" data. For the sake of this test, the "on disk" data |
|
1758 /// does not actually come from the disk, but it's opaque to the code being |
|
1759 /// tested. |
|
1760 #[test] |
|
1761 fn test_on_disk() -> Result<(), DirstateError> { |
|
1762 // First let's create some data to put "on disk" |
|
1763 let mut map = OwningDirstateMap::new_empty(vec![]); |
|
1764 |
|
1765 // A file that was just added |
|
1766 map.set_tracked(p(b"some/nested/added"))?; |
|
1767 map.copy_map_insert(p(b"some/nested/added"), p(b"added_copy_source"))?; |
|
1768 |
|
1769 // A file that was removed |
|
1770 map.reset_state( |
|
1771 p(b"some/nested/removed"), |
|
1772 false, |
|
1773 true, |
|
1774 false, |
|
1775 false, |
|
1776 None, |
|
1777 )?; |
|
1778 // Only present in p2 |
|
1779 map.reset_state( |
|
1780 p(b"other/p2_info_only"), |
|
1781 false, |
|
1782 false, |
|
1783 true, |
|
1784 false, |
|
1785 None, |
|
1786 )?; |
|
1787 map.copy_map_insert( |
|
1788 p(b"other/p2_info_only"), |
|
1789 p(b"other/p2_info_copy_source"), |
|
1790 )?; |
|
1791 // A file that was merged |
|
1792 map.reset_state(p(b"merged"), true, true, true, false, None)?; |
|
1793 // A file that is added, with info from p2 |
|
1794 // XXX is that actually possible? |
|
1795 map.reset_state( |
|
1796 p(b"other/added_with_p2"), |
|
1797 true, |
|
1798 false, |
|
1799 true, |
|
1800 false, |
|
1801 None, |
|
1802 )?; |
|
1803 // One layer without any files to test deletion cascade |
|
1804 // A clean file |
|
1805 map.reset_state( |
|
1806 p(b"some/other/nested/clean"), |
|
1807 true, |
|
1808 true, |
|
1809 false, |
|
1810 false, |
|
1811 None, |
|
1812 )?; |
|
1813 |
|
1814 let (packed, metadata, _should_append, _old_data_size) = |
|
1815 map.pack_v2(false)?; |
|
1816 let packed_len = packed.len(); |
|
1817 assert!(packed_len > 0); |
|
1818 |
|
1819 // Recreate "from disk" |
|
1820 let mut map = OwningDirstateMap::new_v2( |
|
1821 packed, |
|
1822 packed_len, |
|
1823 metadata.as_bytes(), |
|
1824 )?; |
|
1825 |
|
1826 // Check that everything is accounted for |
|
1827 assert!(map.contains_key(p(b"some/nested/added"))?); |
|
1828 assert!(map.contains_key(p(b"some/nested/removed"))?); |
|
1829 assert!(map.contains_key(p(b"merged"))?); |
|
1830 assert!(map.contains_key(p(b"other/p2_info_only"))?); |
|
1831 assert!(map.contains_key(p(b"other/added_with_p2"))?); |
|
1832 assert!(map.contains_key(p(b"some/other/nested/clean"))?); |
|
1833 assert_eq!( |
|
1834 map.copy_map_get(p(b"some/nested/added"))?, |
|
1835 Some(p(b"added_copy_source")) |
|
1836 ); |
|
1837 assert_eq!( |
|
1838 map.copy_map_get(p(b"other/p2_info_only"))?, |
|
1839 Some(p(b"other/p2_info_copy_source")) |
|
1840 ); |
|
1841 assert_eq!(tracked_descendants(&map, b"some"), 2); |
|
1842 assert_eq!(descendants_with_an_entry(&map, b"some"), 3); |
|
1843 assert_eq!(tracked_descendants(&map, b"other"), 1); |
|
1844 assert_eq!(descendants_with_an_entry(&map, b"other"), 2); |
|
1845 assert_eq!(tracked_descendants(&map, b"some/other"), 1); |
|
1846 assert_eq!(descendants_with_an_entry(&map, b"some/other"), 1); |
|
1847 assert_eq!(tracked_descendants(&map, b"some/other/nested"), 1); |
|
1848 assert_eq!(descendants_with_an_entry(&map, b"some/other/nested"), 1); |
|
1849 assert_eq!(tracked_descendants(&map, b"some/nested"), 1); |
|
1850 assert_eq!(descendants_with_an_entry(&map, b"some/nested"), 2); |
|
1851 assert_eq!(map.len(), 6); |
|
1852 assert_eq!(map.get_map().unreachable_bytes, 0); |
|
1853 assert_eq!(map.copy_map_len(), 2); |
|
1854 |
|
1855 // Shouldn't change anything since it's already not tracked |
|
1856 map.set_untracked(p(b"some/nested/removed"))?; |
|
1857 assert_eq!(map.get_map().unreachable_bytes, 0); |
|
1858 |
|
1859 match map.get_map().root { |
|
1860 ChildNodes::InMemory(_) => { |
|
1861 panic!("root should not have been mutated") |
|
1862 } |
|
1863 _ => (), |
|
1864 } |
|
1865 // We haven't mutated enough (nothing, actually), we should still be in |
|
1866 // the append strategy |
|
1867 assert!(map.get_map().write_should_append()); |
|
1868 |
|
1869 // But this mutates the structure, so there should be unreachable_bytes |
|
1870 assert!(map.set_untracked(p(b"some/nested/added"))?); |
|
1871 let unreachable_bytes = map.get_map().unreachable_bytes; |
|
1872 assert!(unreachable_bytes > 0); |
|
1873 |
|
1874 match map.get_map().root { |
|
1875 ChildNodes::OnDisk(_) => panic!("root should have been mutated"), |
|
1876 _ => (), |
|
1877 } |
|
1878 |
|
1879 // This should not mutate the structure either, since `root` has |
|
1880 // already been mutated along with its direct children. |
|
1881 map.set_untracked(p(b"merged"))?; |
|
1882 assert_eq!(map.get_map().unreachable_bytes, unreachable_bytes); |
|
1883 |
|
1884 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() { |
|
1885 NodeRef::InMemory(_, _) => { |
|
1886 panic!("'other/added_with_p2' should not have been mutated") |
|
1887 } |
|
1888 _ => (), |
|
1889 } |
|
1890 // But this should, since it's in a different path |
|
1891 // than `<root>some/nested/add` |
|
1892 map.set_untracked(p(b"other/added_with_p2"))?; |
|
1893 assert!(map.get_map().unreachable_bytes > unreachable_bytes); |
|
1894 |
|
1895 match map.get_map().get_node(p(b"other/added_with_p2"))?.unwrap() { |
|
1896 NodeRef::OnDisk(_) => { |
|
1897 panic!("'other/added_with_p2' should have been mutated") |
|
1898 } |
|
1899 _ => (), |
|
1900 } |
|
1901 |
|
1902 // We have rewritten most of the tree, we should create a new file |
|
1903 assert!(!map.get_map().write_should_append()); |
|
1904 |
|
1905 Ok(()) |
|
1906 } |
|
1907 } |