forked from mirrors/jj
DescendantRebaser: also remove old heads
This patch teaches `DescendantRebaser` to also update heads. That's done at the end of the rebase (when `rebase_next()` starts returning `None`), which is a little weird. We should probably change the interface, but this will do for now. With this change, we should no longer need to remove hidden heads when the transaction commits. That will remove one of the last bits of dependence on evolution from most commands (#32).
This commit is contained in:
parent
e26d21dc18
commit
b7acbae168
2 changed files with 357 additions and 34 deletions
|
@ -119,9 +119,14 @@ pub struct DescendantRebaser<'settings, 'repo> {
|
|||
// want to rebase them. Instead, we record them in `replacements` when we visit them. That way,
|
||||
// their descendants will be rebased correctly.
|
||||
to_skip: HashSet<CommitId>,
|
||||
new_commits: HashSet<CommitId>,
|
||||
rebased: HashMap<CommitId, CommitId>,
|
||||
// Names of branches where local target includes the commit id in the key.
|
||||
branches: HashMap<CommitId, Vec<String>>,
|
||||
// Parents of rebased/abandoned commit that should become new heads once their descendants
|
||||
// have been rebased.
|
||||
heads_to_add: HashSet<CommitId>,
|
||||
heads_to_remove: Vec<CommitId>,
|
||||
}
|
||||
|
||||
impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
||||
|
@ -135,8 +140,15 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
.union(&RevsetExpression::commits(
|
||||
abandoned.iter().cloned().collect(),
|
||||
));
|
||||
let new_commits_expression =
|
||||
RevsetExpression::commits(rewritten.values().flatten().cloned().collect());
|
||||
let heads_to_add_expression = old_commits_expression
|
||||
.parents()
|
||||
.minus(&old_commits_expression);
|
||||
let heads_to_add = heads_to_add_expression
|
||||
.evaluate(mut_repo.as_repo_ref())
|
||||
.unwrap()
|
||||
.iter()
|
||||
.commit_ids()
|
||||
.collect();
|
||||
|
||||
let to_visit_expression = old_commits_expression.descendants();
|
||||
let to_visit_revset = to_visit_expression
|
||||
|
@ -145,6 +157,8 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
let to_visit = to_visit_revset.iter().commit_ids().collect_vec();
|
||||
drop(to_visit_revset);
|
||||
|
||||
let new_commits_expression =
|
||||
RevsetExpression::commits(rewritten.values().flatten().cloned().collect());
|
||||
let ancestors_expression =
|
||||
to_visit_expression.intersection(&new_commits_expression.ancestors());
|
||||
let ancestors_revset = ancestors_expression
|
||||
|
@ -154,6 +168,8 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
to_skip.extend(ancestors_revset.iter().commit_ids());
|
||||
drop(ancestors_revset);
|
||||
|
||||
let new_commits = rewritten.values().flatten().cloned().collect();
|
||||
|
||||
let mut new_parents = HashMap::new();
|
||||
let mut divergent = HashMap::new();
|
||||
for (old_commit, new_commits) in rewritten {
|
||||
|
@ -193,8 +209,11 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
divergent,
|
||||
to_visit,
|
||||
to_skip,
|
||||
new_commits,
|
||||
rebased: Default::default(),
|
||||
branches,
|
||||
heads_to_add,
|
||||
heads_to_remove: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -233,11 +252,7 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
)
|
||||
}
|
||||
|
||||
fn update_branches_and_checkout(
|
||||
&mut self,
|
||||
old_commit_id: CommitId,
|
||||
new_commit_ids: Vec<CommitId>,
|
||||
) {
|
||||
fn update_references(&mut self, old_commit_id: CommitId, new_commit_ids: Vec<CommitId>) {
|
||||
if *self.mut_repo.view().checkout() == old_commit_id {
|
||||
// We arbitrarily pick a new checkout among the candidates.
|
||||
let new_commit_id = new_commit_ids[0].clone();
|
||||
|
@ -281,8 +296,14 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.heads_to_add.remove(&old_commit_id);
|
||||
if !self.new_commits.contains(&old_commit_id) {
|
||||
self.heads_to_remove.push(old_commit_id);
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Perhaps the interface since it's not just about rebasing commits.
|
||||
pub fn rebase_next(&mut self) -> Option<RebasedDescendant> {
|
||||
while let Some(old_commit_id) = self.to_visit.pop() {
|
||||
if let Some(new_parent_ids) = self.new_parents.get(&old_commit_id).cloned() {
|
||||
|
@ -290,13 +311,13 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
// (i.e. it's part of the input for this rebase). We don't need
|
||||
// to rebase it, but we still want to update branches pointing
|
||||
// to the old commit.
|
||||
self.update_branches_and_checkout(old_commit_id, new_parent_ids);
|
||||
self.update_references(old_commit_id, new_parent_ids);
|
||||
continue;
|
||||
}
|
||||
if let Some(divergent_ids) = self.divergent.get(&old_commit_id).cloned() {
|
||||
// Leave divergent commits in place. Don't update `new_parents` since we don't
|
||||
// want to rebase descendants either.
|
||||
self.update_branches_and_checkout(old_commit_id, divergent_ids);
|
||||
self.update_references(old_commit_id, divergent_ids);
|
||||
continue;
|
||||
}
|
||||
let old_commit = self.mut_repo.store().get_commit(&old_commit_id).unwrap();
|
||||
|
@ -306,7 +327,7 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
// Update the `new_parents` map so descendants are rebased correctly.
|
||||
self.new_parents
|
||||
.insert(old_commit_id.clone(), new_parent_ids.clone());
|
||||
self.update_branches_and_checkout(old_commit_id, new_parent_ids);
|
||||
self.update_references(old_commit_id, new_parent_ids);
|
||||
continue;
|
||||
} else if new_parent_ids == old_parent_ids {
|
||||
// The commit is already in place.
|
||||
|
@ -327,13 +348,23 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
|||
.map(|new_parent_id| self.mut_repo.store().get_commit(new_parent_id).unwrap())
|
||||
.collect_vec();
|
||||
let new_commit = rebase_commit(self.settings, self.mut_repo, &old_commit, &new_parents);
|
||||
self.update_branches_and_checkout(old_commit_id.clone(), vec![new_commit.id().clone()]);
|
||||
self.update_references(old_commit_id.clone(), vec![new_commit.id().clone()]);
|
||||
self.rebased.insert(old_commit_id, new_commit.id().clone());
|
||||
return Some(RebasedDescendant {
|
||||
old_commit,
|
||||
new_commit,
|
||||
});
|
||||
}
|
||||
let mut view = self.mut_repo.view().store_view().clone();
|
||||
for commit_id in &self.heads_to_remove {
|
||||
view.head_ids.remove(commit_id);
|
||||
}
|
||||
for commit_id in &self.heads_to_add {
|
||||
view.head_ids.insert(commit_id.clone());
|
||||
}
|
||||
self.heads_to_remove.clear();
|
||||
self.heads_to_add.clear();
|
||||
self.mut_repo.set_view(view);
|
||||
None
|
||||
}
|
||||
|
||||
|
|
|
@ -54,11 +54,20 @@ fn test_rebase_descendants_sideways(use_git: bool) {
|
|||
hashset! {},
|
||||
);
|
||||
let new_commit_c = assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_f]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_d, &[&new_commit_c]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f]);
|
||||
let new_commit_d = assert_rebased(rebaser.rebase_next(), &commit_d, &[&new_commit_c]);
|
||||
let new_commit_e = assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 3);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
new_commit_d.id().clone(),
|
||||
new_commit_e.id().clone()
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -88,7 +97,7 @@ fn test_rebase_descendants_forward(use_git: bool) {
|
|||
let commit_d = graph_builder.commit_with_parents(&[&commit_b]);
|
||||
let commit_e = graph_builder.commit_with_parents(&[&commit_d]);
|
||||
let commit_f = graph_builder.commit_with_parents(&[&commit_d]);
|
||||
let _commit_g = graph_builder.commit_with_parents(&[&commit_f]);
|
||||
let commit_g = graph_builder.commit_with_parents(&[&commit_f]);
|
||||
|
||||
let mut rebaser = DescendantRebaser::new(
|
||||
&settings,
|
||||
|
@ -98,11 +107,21 @@ fn test_rebase_descendants_forward(use_git: bool) {
|
|||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_f]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f]);
|
||||
let new_commit_c = assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_f]);
|
||||
let new_commit_e = assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
commit_g.id().clone(),
|
||||
new_commit_c.id().clone(),
|
||||
new_commit_e.id().clone()
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -133,10 +152,15 @@ fn test_rebase_descendants_backward(use_git: bool) {
|
|||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_b]);
|
||||
let new_commit_d = assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_b]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), new_commit_d.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -175,7 +199,7 @@ fn test_rebase_descendants_internal_merge(use_git: bool) {
|
|||
);
|
||||
let new_commit_c = assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_f]);
|
||||
let new_commit_d = assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_f]);
|
||||
assert_rebased(
|
||||
let new_commit_e = assert_rebased(
|
||||
rebaser.rebase_next(),
|
||||
&commit_e,
|
||||
&[&new_commit_c, &new_commit_d],
|
||||
|
@ -183,6 +207,11 @@ fn test_rebase_descendants_internal_merge(use_git: bool) {
|
|||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 3);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! { repo.view().checkout().clone(), new_commit_e.id().clone() }
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -220,10 +249,15 @@ fn test_rebase_descendants_external_merge(use_git: bool) {
|
|||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f, &commit_d]);
|
||||
let new_commit_e = assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f, &commit_d]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), new_commit_e.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -257,12 +291,58 @@ fn test_rebase_descendants_abandon(use_git: bool) {
|
|||
hashmap! {},
|
||||
hashset! {commit_b.id().clone(), commit_e.id().clone()},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_a]);
|
||||
let new_commit_c = assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_a]);
|
||||
let new_commit_d = assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_a]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_f, &[&new_commit_d]);
|
||||
let new_commit_f = assert_rebased(rebaser.rebase_next(), &commit_f, &[&new_commit_d]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 3);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
new_commit_c.id().clone(),
|
||||
new_commit_f.id().clone()
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_rebase_descendants_abandon_no_descendants(use_git: bool) {
|
||||
let settings = testutils::user_settings();
|
||||
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
||||
|
||||
// Commit B and C were abandoned. Commit A should become a head.
|
||||
//
|
||||
// C
|
||||
// B
|
||||
// A
|
||||
let mut tx = repo.start_transaction("test");
|
||||
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
||||
let commit_a = graph_builder.initial_commit();
|
||||
let commit_b = graph_builder.commit_with_parents(&[&commit_a]);
|
||||
let commit_c = graph_builder.commit_with_parents(&[&commit_b]);
|
||||
|
||||
let mut rebaser = DescendantRebaser::new(
|
||||
&settings,
|
||||
tx.mut_repo(),
|
||||
hashmap! {},
|
||||
hashset! {commit_b.id().clone(), commit_c.id().clone()},
|
||||
);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 0);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
commit_a.id().clone(),
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -294,10 +374,15 @@ fn test_rebase_descendants_abandon_and_replace(use_git: bool) {
|
|||
hashmap! {commit_b.id().clone() => hashset!{commit_e.id().clone()}},
|
||||
hashset! {commit_c.id().clone()},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_e]);
|
||||
let new_commit_d = assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_e]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), new_commit_d.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -328,10 +413,15 @@ fn test_rebase_descendants_abandon_degenerate_merge(use_git: bool) {
|
|||
hashmap! {},
|
||||
hashset! {commit_b.id().clone()},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_c]);
|
||||
let new_commit_d = assert_rebased(rebaser.rebase_next(), &commit_d, &[&commit_c]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), new_commit_d.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -366,7 +456,7 @@ fn test_rebase_descendants_abandon_widen_merge(use_git: bool) {
|
|||
hashmap! {},
|
||||
hashset! {commit_e.id().clone()},
|
||||
);
|
||||
assert_rebased(
|
||||
let new_commit_f = assert_rebased(
|
||||
rebaser.rebase_next(),
|
||||
&commit_f,
|
||||
&[&commit_b, &commit_c, &commit_d],
|
||||
|
@ -374,6 +464,11 @@ fn test_rebase_descendants_abandon_widen_merge(use_git: bool) {
|
|||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 1);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), new_commit_f.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -409,11 +504,20 @@ fn test_rebase_descendants_multiple_sideways(use_git: bool) {
|
|||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_f]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f]);
|
||||
let new_commit_c = assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_f]);
|
||||
let new_commit_e = assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_f]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
new_commit_c.id().clone(),
|
||||
new_commit_e.id().clone()
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -447,11 +551,61 @@ fn test_rebase_descendants_multiple_swap(use_git: bool) {
|
|||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_d]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_b]);
|
||||
let new_commit_c = assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_d]);
|
||||
let new_commit_e = assert_rebased(rebaser.rebase_next(), &commit_e, &[&commit_b]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
new_commit_c.id().clone(),
|
||||
new_commit_e.id().clone()
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
#[test_case(false ; "local backend")]
|
||||
#[test_case(true ; "git backend")]
|
||||
fn test_rebase_descendants_multiple_no_descendants(use_git: bool) {
|
||||
let settings = testutils::user_settings();
|
||||
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
||||
|
||||
// Commit B was replaced by commit C. Commit C was replaced by commit B.
|
||||
//
|
||||
// B C
|
||||
// |/
|
||||
// A
|
||||
let mut tx = repo.start_transaction("test");
|
||||
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
||||
let commit_a = graph_builder.initial_commit();
|
||||
let commit_b = graph_builder.commit_with_parents(&[&commit_a]);
|
||||
let commit_c = graph_builder.commit_with_parents(&[&commit_a]);
|
||||
|
||||
let mut rebaser = DescendantRebaser::new(
|
||||
&settings,
|
||||
tx.mut_repo(),
|
||||
hashmap! {
|
||||
commit_b.id().clone() => hashset!{commit_c.id().clone()},
|
||||
commit_c.id().clone() => hashset!{commit_b.id().clone()},
|
||||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert!(rebaser.rebased().is_empty());
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
commit_b.id().clone(),
|
||||
commit_c.id().clone()
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -462,7 +616,7 @@ fn test_rebase_descendants_multiple_forward_and_backward(use_git: bool) {
|
|||
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
||||
|
||||
// Commit B was replaced by commit D. Commit F was replaced by commit C.
|
||||
// Commit G should be rebased onto commit C. Commit 8 should be rebased onto
|
||||
// Commit G should be rebased onto commit C. Commit H should be rebased onto
|
||||
// commit D. Commits C-D should be left alone since they're ancestors of D.
|
||||
// Commit E should be left alone since its already in place (as a descendant of
|
||||
// D).
|
||||
|
@ -471,7 +625,7 @@ fn test_rebase_descendants_multiple_forward_and_backward(use_git: bool) {
|
|||
// F
|
||||
// E
|
||||
// D
|
||||
// C 8
|
||||
// C H
|
||||
// |/
|
||||
// B
|
||||
// A
|
||||
|
@ -495,11 +649,21 @@ fn test_rebase_descendants_multiple_forward_and_backward(use_git: bool) {
|
|||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_g, &[&commit_c]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_h, &[&commit_d]);
|
||||
let new_commit_g = assert_rebased(rebaser.rebase_next(), &commit_g, &[&commit_c]);
|
||||
let new_commit_h = assert_rebased(rebaser.rebase_next(), &commit_h, &[&commit_d]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
commit_e.id().clone(),
|
||||
new_commit_g.id().clone(),
|
||||
new_commit_h.id().clone()
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -553,11 +717,23 @@ fn test_rebase_descendants_divergent_rewrite(use_git: bool) {
|
|||
},
|
||||
hashset! {},
|
||||
);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_b2]);
|
||||
assert_rebased(rebaser.rebase_next(), &commit_g, &[&commit_f2]);
|
||||
let new_commit_c = assert_rebased(rebaser.rebase_next(), &commit_c, &[&commit_b2]);
|
||||
let new_commit_g = assert_rebased(rebaser.rebase_next(), &commit_g, &[&commit_f2]);
|
||||
assert!(rebaser.rebase_next().is_none());
|
||||
assert_eq!(rebaser.rebased().len(), 2);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
new_commit_c.id().clone(),
|
||||
commit_d2.id().clone(),
|
||||
commit_d3.id().clone(),
|
||||
commit_e.id().clone(),
|
||||
new_commit_g.id().clone(),
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -643,6 +819,46 @@ fn test_rebase_descendants_basic_branch_update() {
|
|||
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
||||
let commit_a = graph_builder.initial_commit();
|
||||
let commit_b = graph_builder.commit_with_parents(&[&commit_a]);
|
||||
tx.mut_repo()
|
||||
.set_local_branch("main".to_string(), RefTarget::Normal(commit_b.id().clone()));
|
||||
let repo = tx.commit();
|
||||
|
||||
let mut tx = repo.start_transaction("test");
|
||||
let commit_b2 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &commit_b)
|
||||
.write_to_repo(tx.mut_repo());
|
||||
tx.mut_repo()
|
||||
.create_descendant_rebaser(&settings)
|
||||
.rebase_all();
|
||||
assert_eq!(
|
||||
tx.mut_repo().get_local_branch("main"),
|
||||
Some(RefTarget::Normal(commit_b2.id().clone()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), commit_b2.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rebase_descendants_basic_branch_update_with_non_local_branch() {
|
||||
let settings = testutils::user_settings();
|
||||
let (_temp_dir, repo) = testutils::init_repo(&settings, false);
|
||||
|
||||
// Branch "main" points to branch B. B gets rewritten as B2. Branch main should
|
||||
// be updated to point to B2. Remote branch main@origin and tag v1 should not
|
||||
// get updated.
|
||||
//
|
||||
// B2 main
|
||||
// B main main@origin v1 | B main@origin v1
|
||||
// | => |/
|
||||
// A A
|
||||
let mut tx = repo.start_transaction("test");
|
||||
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
||||
let commit_a = graph_builder.initial_commit();
|
||||
let commit_b = graph_builder.commit_with_parents(&[&commit_a]);
|
||||
tx.mut_repo()
|
||||
.set_local_branch("main".to_string(), RefTarget::Normal(commit_b.id().clone()));
|
||||
tx.mut_repo().set_remote_branch(
|
||||
|
@ -674,6 +890,49 @@ fn test_rebase_descendants_basic_branch_update() {
|
|||
Some(RefTarget::Normal(commit_b.id().clone()))
|
||||
);
|
||||
|
||||
// Commit B is still visible because the remote branch points to it
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), commit_b.id().clone(), commit_b2.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rebase_descendants_update_branch_after_abandon() {
|
||||
let settings = testutils::user_settings();
|
||||
let (_temp_dir, repo) = testutils::init_repo(&settings, false);
|
||||
|
||||
// Branch "main" points to branch B. B is then abandoned. Branch main should
|
||||
// be updated to point to A.
|
||||
//
|
||||
// B main
|
||||
// | => A main
|
||||
// A
|
||||
let mut tx = repo.start_transaction("test");
|
||||
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
||||
let commit_a = graph_builder.initial_commit();
|
||||
let commit_b = graph_builder.commit_with_parents(&[&commit_a]);
|
||||
tx.mut_repo()
|
||||
.set_local_branch("main".to_string(), RefTarget::Normal(commit_b.id().clone()));
|
||||
let repo = tx.commit();
|
||||
|
||||
let mut tx = repo.start_transaction("test");
|
||||
tx.mut_repo().record_abandoned_commit(commit_b.id().clone());
|
||||
tx.mut_repo()
|
||||
.create_descendant_rebaser(&settings)
|
||||
.rebase_all();
|
||||
assert_eq!(
|
||||
tx.mut_repo().get_local_branch("main"),
|
||||
Some(RefTarget::Normal(commit_a.id().clone()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), commit_a.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -724,6 +983,20 @@ fn test_rebase_descendants_update_branches_after_divergent_rewrite() {
|
|||
})
|
||||
);
|
||||
|
||||
// TODO: We should probably either hide B or indicate in the UI why it's still
|
||||
// visible. Alternatively, we could redefine the view's heads to be only the
|
||||
// desired anonymous heads.
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
commit_b.id().clone(),
|
||||
commit_b2.id().clone(),
|
||||
commit_b3.id().clone(),
|
||||
commit_b4.id().clone(),
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -782,6 +1055,20 @@ fn test_rebase_descendants_rewrite_updates_branch_conflict() {
|
|||
})
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {
|
||||
repo.view().checkout().clone(),
|
||||
commit_a.id().clone(),
|
||||
commit_a2.id().clone(),
|
||||
commit_a3.id().clone(),
|
||||
commit_b.id().clone(),
|
||||
commit_b2.id().clone(),
|
||||
commit_b3.id().clone(),
|
||||
commit_c.id().clone(),
|
||||
}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
@ -823,6 +1110,11 @@ fn test_rebase_descendants_rewrite_resolves_branch_conflict() {
|
|||
Some(RefTarget::Normal(commit_b2.id().clone()))
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
*tx.mut_repo().view().heads(),
|
||||
hashset! {repo.view().checkout().clone(), commit_b2.id().clone()}
|
||||
);
|
||||
|
||||
tx.discard();
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue