comparison rust/hg-core/src/revlog/index.rs @ 51397:b01e7d97e167

revlog: add a Rust implementation of `headrevsdiff` Python implementation of `headrevsdiff` can be very slow in the worst case compared with the `heads` computation it replaces, since the latter is done in Rust. Even the average case of this Python implementation is still noticeable in the profiles. This patch makes the computation much much faster by doing it in Rust.
author Arseniy Alekseyev <aalekseyev@janestreet.com>
date Mon, 12 Feb 2024 20:01:27 +0000
parents c4cbb515b006
children 7c6d0b9dde37
comparison
equal deleted inserted replaced
51396:3f37d80d3ab4 51397:b01e7d97e167
552 /// Python-specific shortcut to save on PyList creation 552 /// Python-specific shortcut to save on PyList creation
553 pub fn head_revs_shortcut( 553 pub fn head_revs_shortcut(
554 &self, 554 &self,
555 ) -> Result<Option<Vec<Revision>>, GraphError> { 555 ) -> Result<Option<Vec<Revision>>, GraphError> {
556 self.head_revs_filtered(&HashSet::new(), true) 556 self.head_revs_filtered(&HashSet::new(), true)
557 }
558
559 /// Return the heads removed and added by advancing from `begin` to `end`.
560 /// In revset language, we compute:
561 /// - `heads(:begin)-heads(:end)`
562 /// - `heads(:end)-heads(:begin)`
563 pub fn head_revs_diff(
564 &self,
565 begin: Revision,
566 end: Revision,
567 ) -> Result<(Vec<Revision>, Vec<Revision>), GraphError> {
568 let mut heads_added = vec![];
569 let mut heads_removed = vec![];
570
571 let mut acc = HashSet::new();
572 let Revision(begin) = begin;
573 let Revision(end) = end;
574 let mut i = end;
575
576 while i > begin {
577 // acc invariant:
578 // `j` is in the set iff `j <= i` and it has children
579 // among `i+1..end` (inclusive)
580 if !acc.remove(&i) {
581 heads_added.push(Revision(i));
582 }
583 for Revision(parent) in self.parents(Revision(i))? {
584 acc.insert(parent);
585 }
586 i -= 1;
587 }
588
589 // At this point `acc` contains old revisions that gained new children.
590 // We need to check if they had any children before. If not, those
591 // revisions are the removed heads.
592 while !acc.is_empty() {
593 // acc invariant:
594 // `j` is in the set iff `j <= i` and it has children
595 // among `begin+1..end`, but not among `i+1..begin` (inclusive)
596
597 assert!(i >= -1); // yes, `-1` can also be a head if the repo is empty
598 if acc.remove(&i) {
599 heads_removed.push(Revision(i));
600 }
601 for Revision(parent) in self.parents(Revision(i))? {
602 acc.remove(&parent);
603 }
604 i -= 1;
605 }
606
607 Ok((heads_removed, heads_added))
557 } 608 }
558 609
559 /// Return the head revisions of this index 610 /// Return the head revisions of this index
560 pub fn head_revs_filtered( 611 pub fn head_revs_filtered(
561 &self, 612 &self,