forked from mirrors/jj
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:
parent
cc9008c6bb
commit
6807407814
2 changed files with 59 additions and 24 deletions
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue