evolution: fix it so pruned commits can be divergent

A pruned commit just indicates that its predecessors should be evolved
onto the pruned commit's parent instead of onto the pruned commit
itself. The pruned commit itself can be divergent. For example, if
there are several pruned sucessors of a commit, then it's unclear
where the predecessor's children should be rebased to.
This commit is contained in:
Martin von Zweigbergk 2020-12-23 17:46:01 -08:00
parent cc9008c6bb
commit 6807407814
2 changed files with 59 additions and 24 deletions

View File

@ -35,6 +35,7 @@ struct State {
/// Contains the subset of the keys in `successors` for which there is a
/// successor with the same change id.
obsolete_commits: HashSet<CommitId>,
pruned_commits: HashSet<CommitId>,
orphan_commits: HashSet<CommitId>,
divergent_changes: HashMap<ChangeId, HashSet<CommitId>>,
}
@ -61,7 +62,7 @@ impl State {
// children of a commit
for commit in &commits {
if commit.is_pruned() {
state.obsolete_commits.insert(commit.id().clone());
state.pruned_commits.insert(commit.id().clone());
}
for predecessor in commit.predecessors() {
if !commits.contains(&predecessor) {
@ -94,6 +95,7 @@ impl State {
}
// Find orphans by walking to the children of obsolete commits
let mut work: Vec<CommitId> = state.obsolete_commits.iter().cloned().collect();
work.extend(state.pruned_commits.iter().cloned());
while !work.is_empty() {
let commit_id = work.pop().unwrap();
for child in children.get(&commit_id).unwrap() {
@ -104,8 +106,12 @@ impl State {
}
state.orphan_commits = state
.orphan_commits
.difference(&state.obsolete_commits)
.map(ToOwned::to_owned)
.iter()
.filter(|commit_id| {
!(state.obsolete_commits.contains(commit_id)
|| state.pruned_commits.contains(commit_id))
})
.cloned()
.collect();
state

View File

@ -83,9 +83,58 @@ fn test_divergent(use_git: bool) {
// A single commit should not be divergent
let original = child_commit(&settings, &repo, &root_commit).write_to_transaction(&mut tx);
assert!(!tx.as_repo().evolution().is_obsolete(original.id()));
assert!(!tx.as_repo().evolution().is_divergent(original.change_id()));
// Commits with the same change id are divergent, including the original commit
// (it's the change that's divergent)
child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.write_to_transaction(&mut tx);
child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.write_to_transaction(&mut tx);
assert!(tx.as_repo().evolution().is_divergent(original.change_id()));
tx.discard();
}
#[test_case(false ; "local store")]
#[test_case(true ; "git store")]
fn test_divergent_pruned(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 original = child_commit(&settings, &repo, &root_commit).write_to_transaction(&mut tx);
// Pruned commits are also divergent (because it's unclear where descendants
// should be evolved to).
child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.set_pruned(true)
.write_to_transaction(&mut tx);
child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.set_pruned(true)
.write_to_transaction(&mut tx);
assert!(tx.as_repo().evolution().is_divergent(original.change_id()));
tx.discard();
}
#[test_case(false ; "local store")]
#[test_case(true ; "git store")]
fn test_divergent_duplicate(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");
// Successors with different change id are not divergent
let original = child_commit(&settings, &repo, &root_commit).write_to_transaction(&mut tx);
let cherry_picked1 = child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.write_to_transaction(&mut tx);
@ -101,26 +150,6 @@ fn test_divergent(use_git: bool) {
.as_repo()
.evolution()
.is_divergent(cherry_picked2.change_id()));
// Commits with the same change id are divergent, including the original commit
// (it's the change that's is divergent)
let rewritten1 = child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.write_to_transaction(&mut tx);
let rewritten2 = child_commit(&settings, &repo, &root_commit)
.set_predecessors(vec![original.id().clone()])
.set_change_id(original.change_id().clone())
.write_to_transaction(&mut tx);
assert!(tx.as_repo().evolution().is_divergent(original.change_id()));
assert!(tx
.as_repo()
.evolution()
.is_divergent(rewritten1.change_id()));
assert!(tx
.as_repo()
.evolution()
.is_divergent(rewritten2.change_id()));
tx.discard();
}