2021-08-16 02:40:55 +00:00
|
|
|
// Copyright 2021 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
2021-08-29 05:18:33 +00:00
|
|
|
use jujutsu_lib::commit_builder::CommitBuilder;
|
2021-09-17 23:10:32 +00:00
|
|
|
use jujutsu_lib::op_store::RefTarget;
|
2021-08-29 05:18:33 +00:00
|
|
|
use jujutsu_lib::repo_path::RepoPath;
|
2021-09-29 18:27:58 +00:00
|
|
|
use jujutsu_lib::rewrite::{update_branches_after_rewrite, DescendantRebaser};
|
2021-08-16 02:40:55 +00:00
|
|
|
use jujutsu_lib::testutils;
|
2021-09-29 18:27:58 +00:00
|
|
|
use jujutsu_lib::testutils::{assert_rebased, CommitGraphBuilder};
|
2021-09-20 03:29:53 +00:00
|
|
|
use maplit::{hashmap, hashset};
|
2021-08-16 02:40:55 +00:00
|
|
|
use test_case::test_case;
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-08-16 02:40:55 +00:00
|
|
|
fn test_rebase_descendants_sideways(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 was replaced by commit 6. Commits 3-5 should be rebased.
|
|
|
|
//
|
|
|
|
// 6
|
|
|
|
// | 4
|
|
|
|
// | 3 5
|
|
|
|
// | |/
|
|
|
|
// | 2
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit3]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-15 15:54:55 +00:00
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit2.id().clone() => commit6.id().clone()
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
let new_commit3 = assert_rebased(rebaser.rebase_next(), &commit3, &[&commit6]);
|
|
|
|
assert_rebased(rebaser.rebase_next(), &commit4, &[&new_commit3]);
|
|
|
|
assert_rebased(rebaser.rebase_next(), &commit5, &[&commit6]);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 3);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-08-16 02:40:55 +00:00
|
|
|
fn test_rebase_descendants_forward(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
2021-09-15 15:54:55 +00:00
|
|
|
// Commit 2 was replaced by commit 6. Commits 3 and 5 should be rebased onto 6.
|
|
|
|
// Commit 4 does not get rebased because it's an ancestor of the
|
|
|
|
// destination. Commit 7 does not get replaced because it's already in
|
|
|
|
// place.
|
2021-08-16 02:40:55 +00:00
|
|
|
//
|
2021-09-15 15:54:55 +00:00
|
|
|
// 7
|
|
|
|
// 6 5
|
|
|
|
// |/
|
|
|
|
// 4 3
|
2021-08-16 02:40:55 +00:00
|
|
|
// |/
|
|
|
|
// 2
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
2021-09-15 15:54:55 +00:00
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit4]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit4]);
|
2021-09-20 04:32:58 +00:00
|
|
|
let _commit7 = graph_builder.commit_with_parents(&[&commit6]);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-15 15:54:55 +00:00
|
|
|
hashmap! {
|
|
|
|
commit2.id().clone() =>
|
2021-09-20 03:29:53 +00:00
|
|
|
commit6.id().clone()
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit3, &[&commit6]);
|
|
|
|
assert_rebased(rebaser.rebase_next(), &commit5, &[&commit6]);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
2021-09-15 15:54:55 +00:00
|
|
|
assert_eq!(rebaser.rebased().len(), 2);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-08-16 02:40:55 +00:00
|
|
|
fn test_rebase_descendants_backward(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 3 was replaced by commit 2. Commit 4 should be rebased.
|
|
|
|
//
|
|
|
|
// 4
|
|
|
|
// 3
|
|
|
|
// 2
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit3]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-15 15:54:55 +00:00
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit3.id().clone() => commit2.id().clone()
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit4, &[&commit2]);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 1);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-08-16 02:40:55 +00:00
|
|
|
fn test_rebase_descendants_internal_merge(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 was replaced by commit 6. Commits 3-5 should be rebased.
|
|
|
|
//
|
|
|
|
// 6
|
|
|
|
// | 5
|
|
|
|
// | |\
|
|
|
|
// | 3 4
|
|
|
|
// | |/
|
|
|
|
// | 2
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit3, &commit4]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-15 15:54:55 +00:00
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit2.id().clone() => commit6.id().clone()
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
let new_commit3 = assert_rebased(rebaser.rebase_next(), &commit3, &[&commit6]);
|
|
|
|
let new_commit4 = assert_rebased(rebaser.rebase_next(), &commit4, &[&commit6]);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert_rebased(
|
|
|
|
rebaser.rebase_next(),
|
|
|
|
&commit5,
|
2021-09-29 18:27:58 +00:00
|
|
|
&[&new_commit3, &new_commit4],
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 3);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-08-16 02:40:55 +00:00
|
|
|
fn test_rebase_descendants_external_merge(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 3 was replaced by commit 6. Commits 5 should be rebased. The rebased
|
|
|
|
// commit 5 should have 6 as first parent and commit 4 as second parent.
|
|
|
|
//
|
|
|
|
// 6
|
|
|
|
// | 5
|
|
|
|
// | |\
|
|
|
|
// | 3 4
|
|
|
|
// | |/
|
|
|
|
// | 2
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit3, &commit4]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-15 15:54:55 +00:00
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit3.id().clone() => commit6.id().clone()
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit5, &[&commit6, &commit4]);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 1);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-09-20 03:29:53 +00:00
|
|
|
fn test_rebase_descendants_abandon(use_git: bool) {
|
2021-08-16 02:40:55 +00:00
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
2021-09-20 03:29:53 +00:00
|
|
|
// Commit 2 and commit 5 were abandoned. Commit 3 and commit 4 should get
|
|
|
|
// rebased onto commit 1. Commit 6 should get rebased onto the new commit 4.
|
|
|
|
//
|
|
|
|
// 6
|
|
|
|
// 5
|
|
|
|
// 4 3
|
|
|
|
// |/
|
|
|
|
// 2
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit4]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit5]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
|
|
|
hashmap! {},
|
|
|
|
hashset! {commit2.id().clone(), commit5.id().clone()},
|
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit3, &[&commit1]);
|
|
|
|
let new_commit4 = assert_rebased(rebaser.rebase_next(), &commit4, &[&commit1]);
|
|
|
|
assert_rebased(rebaser.rebase_next(), &commit6, &[&new_commit4]);
|
2021-09-20 03:29:53 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 3);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
|
|
|
fn test_rebase_descendants_abandon_and_replace(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 was replaced by commit 5. Commit 3 was abandoned. Commit 4 should
|
|
|
|
// get rebased onto commit 5.
|
|
|
|
//
|
|
|
|
// 4
|
|
|
|
// 3
|
|
|
|
// 5 2
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit3]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
|
|
|
hashmap! {commit2.id().clone() => commit5.id().clone()},
|
|
|
|
hashset! {commit3.id().clone()},
|
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit4, &[&commit5]);
|
2021-09-20 03:29:53 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 1);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
|
|
|
fn test_rebase_descendants_abandon_degenerate_merge(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 was abandoned. Commit 4 should get rebased to have only 3 as parent
|
|
|
|
// (not 1 and 3).
|
2021-08-16 02:40:55 +00:00
|
|
|
//
|
|
|
|
// 4
|
|
|
|
// |\
|
|
|
|
// 2 3
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit2, &commit3]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-20 03:29:53 +00:00
|
|
|
hashmap! {},
|
|
|
|
hashset! {commit2.id().clone()},
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit4, &[&commit3]);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 1);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-09-20 03:29:53 +00:00
|
|
|
fn test_rebase_descendants_abandon_widen_merge(use_git: bool) {
|
2021-08-16 02:40:55 +00:00
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
2021-09-20 03:29:53 +00:00
|
|
|
// Commit 5 was abandoned. Commit 6 should get rebased to have 2, 3, and 4 as
|
|
|
|
// parents (in that order).
|
2021-08-16 02:40:55 +00:00
|
|
|
//
|
|
|
|
// 6
|
|
|
|
// |\
|
|
|
|
// 5 \
|
|
|
|
// |\ \
|
|
|
|
// 2 3 4
|
|
|
|
// \|/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit2, &commit3]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit5, &commit4]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-20 03:29:53 +00:00
|
|
|
hashmap! {},
|
|
|
|
hashset! {commit5.id().clone()},
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert_rebased(
|
|
|
|
rebaser.rebase_next(),
|
|
|
|
&commit6,
|
2021-09-29 18:27:58 +00:00
|
|
|
&[&commit2, &commit3, &commit4],
|
2021-08-16 02:40:55 +00:00
|
|
|
);
|
2021-09-15 22:20:37 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 1);
|
2021-08-16 02:40:55 +00:00
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
2021-08-29 05:18:33 +00:00
|
|
|
|
2021-09-15 15:54:55 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
|
|
|
fn test_rebase_descendants_multiple_sideways(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 and commit 4 were both replaced by commit 6. Commit 3 and commit 5
|
|
|
|
// should get rebased onto it.
|
|
|
|
//
|
|
|
|
// 3 5
|
|
|
|
// 2 4 6
|
|
|
|
// | |/
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit4]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit2.id().clone() => commit6.id().clone(),
|
|
|
|
commit4.id().clone() => commit6.id().clone(),
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-09-15 15:54:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit3, &[&commit6]);
|
|
|
|
assert_rebased(rebaser.rebase_next(), &commit5, &[&commit6]);
|
2021-09-15 15:54:55 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 2);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
|
|
|
fn test_rebase_descendants_multiple_swap(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 was replaced by commit 4 and commit 4 was replaced by commit 2.
|
|
|
|
// Commit 3 and commit 5 should swap places.
|
|
|
|
//
|
|
|
|
// 3 5
|
|
|
|
// 2 4
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit4]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit2.id().clone() => commit4.id().clone(),
|
|
|
|
commit4.id().clone() => commit2.id().clone(),
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-09-15 15:54:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit3, &[&commit4]);
|
|
|
|
assert_rebased(rebaser.rebase_next(), &commit5, &[&commit2]);
|
2021-09-15 15:54:55 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 2);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
|
|
|
fn test_rebase_descendants_multiple_forward_and_backward(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 was replaced by commit 4 and commit 6 was replaced by commit 3.
|
|
|
|
// Commit 7 should be rebased onto commit 3. Commit 8 should be rebased onto
|
|
|
|
// commit 4. Commits 3-4 should be left alone since they're ancestors of 4.
|
|
|
|
// Commit 5 should be left alone since its already in place (as a descendant of
|
|
|
|
// 4).
|
|
|
|
//
|
|
|
|
// 7
|
|
|
|
// 6
|
|
|
|
// 5
|
|
|
|
// 4
|
|
|
|
// 3 8
|
|
|
|
// |/
|
|
|
|
// 2
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let mut graph_builder = CommitGraphBuilder::new(&settings, tx.mut_repo());
|
|
|
|
let commit1 = graph_builder.initial_commit();
|
|
|
|
let commit2 = graph_builder.commit_with_parents(&[&commit1]);
|
|
|
|
let commit3 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
let commit4 = graph_builder.commit_with_parents(&[&commit3]);
|
|
|
|
let commit5 = graph_builder.commit_with_parents(&[&commit4]);
|
|
|
|
let commit6 = graph_builder.commit_with_parents(&[&commit5]);
|
|
|
|
let commit7 = graph_builder.commit_with_parents(&[&commit6]);
|
|
|
|
let commit8 = graph_builder.commit_with_parents(&[&commit2]);
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit2.id().clone() => commit4.id().clone(),
|
|
|
|
commit6.id().clone() => commit3.id().clone(),
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-09-15 15:54:55 +00:00
|
|
|
);
|
2021-09-29 18:27:58 +00:00
|
|
|
assert_rebased(rebaser.rebase_next(), &commit7, &[&commit3]);
|
|
|
|
assert_rebased(rebaser.rebase_next(), &commit8, &[&commit4]);
|
2021-09-15 15:54:55 +00:00
|
|
|
assert!(rebaser.rebase_next().is_none());
|
|
|
|
assert_eq!(rebaser.rebased().len(), 2);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
#[test_case(false ; "local backend")]
|
|
|
|
#[test_case(true ; "git backend")]
|
2021-08-29 05:18:33 +00:00
|
|
|
fn test_rebase_descendants_contents(use_git: bool) {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, use_git);
|
|
|
|
|
|
|
|
// Commit 2 was replaced by commit 4. Commit 3 should have the changes from
|
|
|
|
// commit 3 and commit 4, but not the changes from commit 2.
|
|
|
|
//
|
|
|
|
// 4
|
|
|
|
// | 3
|
|
|
|
// | 2
|
|
|
|
// |/
|
|
|
|
// 1
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let path1 = RepoPath::from_internal_string("file1");
|
|
|
|
let tree1 = testutils::create_tree(&repo, &[(&path1, "content")]);
|
|
|
|
let commit1 = CommitBuilder::for_new_commit(&settings, repo.store(), tree1.id().clone())
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
let path2 = RepoPath::from_internal_string("file2");
|
|
|
|
let tree2 = testutils::create_tree(&repo, &[(&path2, "content")]);
|
|
|
|
let commit2 = CommitBuilder::for_new_commit(&settings, repo.store(), tree2.id().clone())
|
|
|
|
.set_parents(vec![commit1.id().clone()])
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
let path3 = RepoPath::from_internal_string("file3");
|
|
|
|
let tree3 = testutils::create_tree(&repo, &[(&path3, "content")]);
|
|
|
|
let commit3 = CommitBuilder::for_new_commit(&settings, repo.store(), tree3.id().clone())
|
|
|
|
.set_parents(vec![commit2.id().clone()])
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
let path4 = RepoPath::from_internal_string("file4");
|
|
|
|
let tree4 = testutils::create_tree(&repo, &[(&path4, "content")]);
|
|
|
|
let commit4 = CommitBuilder::for_new_commit(&settings, repo.store(), tree4.id().clone())
|
|
|
|
.set_parents(vec![commit1.id().clone()])
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
|
|
|
|
let mut rebaser = DescendantRebaser::new(
|
|
|
|
&settings,
|
|
|
|
tx.mut_repo(),
|
2021-09-15 15:54:55 +00:00
|
|
|
hashmap! {
|
2021-09-20 03:29:53 +00:00
|
|
|
commit2.id().clone() => commit4.id().clone()
|
2021-09-15 15:54:55 +00:00
|
|
|
},
|
2021-09-20 03:29:53 +00:00
|
|
|
hashset! {},
|
2021-08-29 05:18:33 +00:00
|
|
|
);
|
|
|
|
rebaser.rebase_all();
|
|
|
|
let rebased = rebaser.rebased();
|
|
|
|
assert_eq!(rebased.len(), 1);
|
|
|
|
let new_commit3 = repo
|
|
|
|
.store()
|
|
|
|
.get_commit(rebased.get(commit3.id()).unwrap())
|
|
|
|
.unwrap();
|
|
|
|
|
|
|
|
assert_eq!(
|
|
|
|
new_commit3.tree().path_value(&path3),
|
|
|
|
commit3.tree().path_value(&path3)
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
new_commit3.tree().path_value(&path4),
|
|
|
|
commit4.tree().path_value(&path4)
|
|
|
|
);
|
|
|
|
assert_ne!(
|
|
|
|
new_commit3.tree().path_value(&path2),
|
|
|
|
commit2.tree().path_value(&path2)
|
|
|
|
);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
2021-09-17 23:10:32 +00:00
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_branches_after_rewrite_basic() {
|
|
|
|
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.
|
|
|
|
//
|
|
|
|
// B main B2 main
|
|
|
|
// | => |
|
|
|
|
// 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(
|
|
|
|
"main".to_string(),
|
|
|
|
"origin".to_string(),
|
|
|
|
RefTarget::Normal(commit_b.id().clone()),
|
|
|
|
);
|
|
|
|
tx.mut_repo()
|
|
|
|
.set_tag("v1".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());
|
|
|
|
update_branches_after_rewrite(tx.mut_repo());
|
|
|
|
assert_eq!(
|
|
|
|
tx.mut_repo().get_local_branch("main"),
|
|
|
|
Some(RefTarget::Normal(commit_b2.id().clone()))
|
|
|
|
);
|
|
|
|
// The remote branch and tag should not get updated
|
|
|
|
assert_eq!(
|
|
|
|
tx.mut_repo().get_remote_branch("main", "origin"),
|
|
|
|
Some(RefTarget::Normal(commit_b.id().clone()))
|
|
|
|
);
|
|
|
|
assert_eq!(
|
|
|
|
tx.mut_repo().get_tag("v1"),
|
|
|
|
Some(RefTarget::Normal(commit_b.id().clone()))
|
|
|
|
);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_branches_after_rewrite_to_conflict() {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, false);
|
|
|
|
|
|
|
|
// Branch "main" points to commit B. B gets rewritten as B2, B3, B4. Branch main
|
|
|
|
// should become a conflict pointing to all of them.
|
|
|
|
//
|
|
|
|
// B4 main?
|
|
|
|
// | B3 main?
|
|
|
|
// B main |/B2 main?
|
|
|
|
// | => |/
|
|
|
|
// 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()));
|
|
|
|
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());
|
|
|
|
// Different description so they're not the same commit
|
|
|
|
let commit_b3 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &commit_b)
|
|
|
|
.set_description("different".to_string())
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
// Different description so they're not the same commit
|
|
|
|
let commit_b4 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &commit_b)
|
|
|
|
.set_description("more different".to_string())
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
update_branches_after_rewrite(tx.mut_repo());
|
|
|
|
assert_eq!(
|
|
|
|
tx.mut_repo().get_local_branch("main"),
|
|
|
|
Some(RefTarget::Conflict {
|
|
|
|
removes: vec![commit_b.id().clone(), commit_b.id().clone()],
|
|
|
|
adds: vec![
|
|
|
|
commit_b2.id().clone(),
|
|
|
|
commit_b3.id().clone(),
|
|
|
|
commit_b4.id().clone()
|
|
|
|
]
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_branches_after_rewrite_update_conflict() {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, false);
|
|
|
|
|
|
|
|
// Branch "main" is a conflict removing commit A and adding commit B and C.
|
|
|
|
// A gets rewritten as A2 and A3. B gets rewritten as B2 and B2. The branch
|
|
|
|
// should become a conflict removing A2, A3, and B, and adding A, B2, B3, C.
|
|
|
|
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.initial_commit();
|
|
|
|
let commit_c = graph_builder.initial_commit();
|
|
|
|
tx.mut_repo().set_local_branch(
|
|
|
|
"main".to_string(),
|
|
|
|
RefTarget::Conflict {
|
|
|
|
removes: vec![commit_a.id().clone()],
|
|
|
|
adds: vec![commit_b.id().clone(), commit_c.id().clone()],
|
|
|
|
},
|
|
|
|
);
|
|
|
|
let repo = tx.commit();
|
|
|
|
|
|
|
|
let mut tx = repo.start_transaction("test");
|
|
|
|
let commit_a2 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &commit_a)
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
// Different description so they're not the same commit
|
|
|
|
let commit_a3 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &commit_a)
|
|
|
|
.set_description("different".to_string())
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
let commit_b2 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &commit_b)
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
// Different description so they're not the same commit
|
|
|
|
let commit_b3 = CommitBuilder::for_rewrite_from(&settings, repo.store(), &commit_b)
|
|
|
|
.set_description("different".to_string())
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
update_branches_after_rewrite(tx.mut_repo());
|
|
|
|
assert_eq!(
|
|
|
|
tx.mut_repo().get_local_branch("main"),
|
|
|
|
Some(RefTarget::Conflict {
|
|
|
|
removes: vec![
|
|
|
|
commit_b.id().clone(),
|
|
|
|
commit_a2.id().clone(),
|
|
|
|
commit_a3.id().clone()
|
|
|
|
],
|
|
|
|
adds: vec![
|
|
|
|
commit_c.id().clone(),
|
|
|
|
commit_b2.id().clone(),
|
|
|
|
commit_b3.id().clone(),
|
|
|
|
commit_a.id().clone()
|
|
|
|
]
|
|
|
|
})
|
|
|
|
);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_update_branches_after_rewrite_resolves_conflict() {
|
|
|
|
let settings = testutils::user_settings();
|
|
|
|
let (_temp_dir, repo) = testutils::init_repo(&settings, false);
|
|
|
|
|
|
|
|
// Branch "main" is a conflict removing ancestor commit A and adding commit B
|
|
|
|
// and C (maybe it moved forward to B locally and moved forward to C
|
|
|
|
// remotely). Now B gets rewritten as B2, which is a descendant of C (maybe
|
|
|
|
// B was automatically rebased on top of the updated remote). That
|
|
|
|
// would result in a conflict removing A and adding B2 and C. However, since C
|
|
|
|
// is a descendant of A, and B2 is a descendant of C, the conflict gets
|
|
|
|
// resolved to B2.
|
|
|
|
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]);
|
|
|
|
tx.mut_repo().set_local_branch(
|
|
|
|
"main".to_string(),
|
|
|
|
RefTarget::Conflict {
|
|
|
|
removes: vec![commit_a.id().clone()],
|
|
|
|
adds: vec![commit_b.id().clone(), commit_c.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)
|
|
|
|
.set_parents(vec![commit_c.id().clone()])
|
|
|
|
.write_to_repo(tx.mut_repo());
|
|
|
|
update_branches_after_rewrite(tx.mut_repo());
|
|
|
|
assert_eq!(
|
|
|
|
tx.mut_repo().get_local_branch("main"),
|
|
|
|
Some(RefTarget::Normal(commit_b2.id().clone()))
|
|
|
|
);
|
|
|
|
|
|
|
|
tx.discard();
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO: Add a test for the following case, which can't happen with our current
|
|
|
|
// evolution-based rewriting.
|
|
|
|
//
|
|
|
|
// 1. Operation 1 points a branch to commit A
|
|
|
|
// 2. Operation 2 repoints the branch to commit B
|
|
|
|
// 3. Operation 3, which is concurrent with operation 2, deletes the branch
|
|
|
|
// 4. Resolved state (operation 4) will have a "-A+B" state for the branch
|
|
|
|
//
|
|
|
|
// Now we hide B and make A visible instead. When that diff is applied to the
|
|
|
|
// branch, the branch state becomes empty and is thus deleted.
|