evolution: walk orphans using index

This actually seems to make it slightly slower, but it fixes an
important bug (we used to evolve only one topological branch per `jj
evolve` call). The slowdown seemed to be on the order of 5% when
evolving 100 commits on git.git's "what's cooking" branch.
This commit is contained in:
Martin von Zweigbergk 2021-03-17 15:58:35 -07:00
parent 783e1f6512
commit 4c3d73ff3b
3 changed files with 93 additions and 28 deletions

View file

@ -484,42 +484,23 @@ pub fn evolve(
evolve_divergent_change(user_settings, &store, mut_repo, listener, &commits);
}
// Dom't reuse the state from above, since the divergence-resolution may have
// Don't reuse the state from above, since the divergence-resolution may have
// created new orphans, or resolved existing orphans.
let orphans: HashSet<Commit> = mut_repo
.evolution()
.state
.orphan_commits
let orphans_topo_order: Vec<_> = mut_repo
.index()
.topo_order(mut_repo.evolution().state.orphan_commits.iter())
.iter()
.map(|id| store.get_commit(&id).unwrap())
.map(|entry| entry.position())
.collect();
let non_heads: HashSet<Commit> = orphans.iter().flat_map(|commit| commit.parents()).collect();
let orphan_heads: HashSet<Commit> = orphans.difference(&non_heads).cloned().collect();
let mut orphans_topo_order = vec![];
for commit in bfs(
orphan_heads,
Box::new(|commit| commit.id().clone()),
Box::new(|commit| {
commit
.parents()
.iter()
.filter(|commit| orphans.contains(commit))
.cloned()
.collect::<Vec<_>>()
}),
) {
orphans_topo_order.push(commit);
}
while !orphans_topo_order.is_empty() {
let orphan = orphans_topo_order.pop().unwrap();
let old_parents = orphan.parents();
for orphan_pos in orphans_topo_order {
let orphan_entry = mut_repo.index().entry_by_pos(orphan_pos);
let mut new_parents = vec![];
let mut ambiguous_new_parents = false;
let evolution = mut_repo.evolution();
for old_parent in &old_parents {
for old_parent in orphan_entry.parents() {
let new_parent_candidates =
evolution.new_parent(mut_repo.as_repo_ref(), old_parent.id());
evolution.new_parent(mut_repo.as_repo_ref(), &old_parent.commit_id());
if new_parent_candidates.len() > 1 {
ambiguous_new_parents = true;
break;
@ -530,6 +511,7 @@ pub fn evolve(
.unwrap(),
);
}
let orphan = store.get_commit(&orphan_entry.commit_id()).unwrap();
if ambiguous_new_parents {
listener.orphan_target_ambiguous(mut_repo, &orphan);
} else {

View file

@ -654,6 +654,13 @@ impl MutableIndex {
) -> Vec<CommitId> {
CompositeIndex(self).heads(candidates)
}
pub fn topo_order<'candidates>(
&self,
input: impl IntoIterator<Item = &'candidates CommitId>,
) -> Vec<IndexEntry> {
CompositeIndex(self).topo_order(input)
}
}
trait IndexSegment {
@ -932,6 +939,19 @@ impl<'a> CompositeIndex<'a> {
}
candidate_positions
}
pub fn topo_order<'input>(
&self,
input: impl IntoIterator<Item = &'input CommitId>,
) -> Vec<IndexEntry<'a>> {
let mut entries_by_generation: Vec<_> = input
.into_iter()
.map(|id| IndexEntryByPosition(self.entry_by_id(id).unwrap()))
.collect();
entries_by_generation.sort();
let entries: Vec<_> = entries_by_generation.into_iter().map(|key| key.0).collect();
entries
}
}
pub struct IndexLevelStats {
@ -949,6 +969,21 @@ pub struct IndexStats {
pub levels: Vec<IndexLevelStats>,
}
#[derive(Eq, PartialEq)]
struct IndexEntryByPosition<'a>(IndexEntry<'a>);
impl Ord for IndexEntryByPosition<'_> {
fn cmp(&self, other: &Self) -> Ordering {
self.0.pos.cmp(&other.0.pos)
}
}
impl PartialOrd for IndexEntryByPosition<'_> {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
#[derive(Eq, PartialEq)]
struct IndexEntryByGeneration<'a>(IndexEntry<'a>);
@ -1292,6 +1327,10 @@ impl PartialEq for IndexEntry<'_> {
impl Eq for IndexEntry<'_> {}
impl<'a> IndexEntry<'a> {
pub fn position(&self) -> u32 {
self.pos
}
pub fn generation_number(&self) -> u32 {
self.source.segment_generation_number(self.local_pos)
}
@ -1449,6 +1488,13 @@ impl ReadonlyIndex {
CompositeIndex(self).heads(candidates)
}
pub fn topo_order<'candidates>(
&self,
input: impl IntoIterator<Item = &'candidates CommitId>,
) -> Vec<IndexEntry> {
CompositeIndex(self).topo_order(input)
}
fn graph_entry(&self, local_pos: u32) -> CommitGraphEntry {
let offset = (local_pos as usize) * self.commit_graph_entry_size;
CommitGraphEntry {

View file

@ -616,6 +616,43 @@ fn test_evolve_pruned_orphan(use_git: bool) {
tx.discard();
}
#[test_case(false ; "local store")]
// #[test_case(true ; "git store")]
fn test_evolve_multiple_orphans(use_git: bool) {
let settings = testutils::user_settings();
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
let root_commit = repo.store().root_commit();
let mut tx = repo.start_transaction("test");
let mut_repo = tx.mut_repo();
let initial = child_commit(&settings, &repo, &root_commit).write_to_repo(mut_repo);
let child = child_commit(&settings, &repo, &initial).write_to_repo(mut_repo);
let grandchild = child_commit(&settings, &repo, &child).write_to_repo(mut_repo);
let grandchild2 = child_commit(&settings, &repo, &child).write_to_repo(mut_repo);
let rewritten = CommitBuilder::for_rewrite_from(&settings, repo.store(), &initial)
.set_description("rewritten".to_string())
.write_to_repo(mut_repo);
let mut listener = RecordingEvolveListener::default();
evolve(&settings, mut_repo, &mut listener);
assert_eq!(listener.evolved_divergents.len(), 0);
assert_eq!(listener.evolved_orphans.len(), 3);
assert_eq!(&listener.evolved_orphans[0].0, &child);
assert_eq!(&listener.evolved_orphans[0].1.parents(), &vec![rewritten]);
assert_eq!(&listener.evolved_orphans[1].0, &grandchild);
assert_eq!(
&listener.evolved_orphans[1].1.parents(),
&vec![listener.evolved_orphans[0].1.clone()]
);
assert_eq!(&listener.evolved_orphans[2].0, &grandchild2);
assert_eq!(
&listener.evolved_orphans[2].1.parents(),
&vec![listener.evolved_orphans[0].1.clone()]
);
tx.discard();
}
#[test_case(false ; "local store")]
// #[test_case(true ; "git store")]
fn test_evolve_divergent(use_git: bool) {