mirror of
https://github.com/martinvonz/jj.git
synced 2024-12-24 12:48:55 +00:00
rewrite: add support for rebasing descendants of multiple rewritten commits
I plan to use this for rebasing descendants of rewritten remote branches (on fetch).
This commit is contained in:
parent
30bcf6508e
commit
ca114d6d7e
5 changed files with 162 additions and 24 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -531,6 +531,7 @@ dependencies = [
|
||||||
"indoc",
|
"indoc",
|
||||||
"itertools 0.10.1",
|
"itertools 0.10.1",
|
||||||
"jujutsu-lib",
|
"jujutsu-lib",
|
||||||
|
"maplit",
|
||||||
"pest",
|
"pest",
|
||||||
"pest_derive",
|
"pest_derive",
|
||||||
"protobuf",
|
"protobuf",
|
||||||
|
|
|
@ -32,6 +32,7 @@ hex = "0.4.2"
|
||||||
indoc = "1.0.3"
|
indoc = "1.0.3"
|
||||||
itertools = "0.10.1"
|
itertools = "0.10.1"
|
||||||
jujutsu-lib = { version = "=0.2.0", path = "lib"}
|
jujutsu-lib = { version = "=0.2.0", path = "lib"}
|
||||||
|
maplit = "1.0.2"
|
||||||
pest = "2.1.3"
|
pest = "2.1.3"
|
||||||
pest_derive = "2.1.0"
|
pest_derive = "2.1.0"
|
||||||
protobuf = { version = "2.22.1", features = ["with-bytes"] }
|
protobuf = { version = "2.22.1", features = ["with-bytes"] }
|
||||||
|
|
|
@ -15,7 +15,6 @@
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::collections::{HashMap, HashSet};
|
||||||
|
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
use maplit::hashmap;
|
|
||||||
|
|
||||||
use crate::backend::CommitId;
|
use crate::backend::CommitId;
|
||||||
use crate::commit::Commit;
|
use crate::commit::Commit;
|
||||||
|
@ -120,10 +119,8 @@ impl<'settings, 'repo> DescendantRebaser<'settings, 'repo> {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
settings: &'settings UserSettings,
|
settings: &'settings UserSettings,
|
||||||
mut_repo: &'repo mut MutableRepo,
|
mut_repo: &'repo mut MutableRepo,
|
||||||
old_parent_id: CommitId,
|
replacements: HashMap<CommitId, Vec<CommitId>>,
|
||||||
new_parent_ids: Vec<CommitId>,
|
|
||||||
) -> DescendantRebaser<'settings, 'repo> {
|
) -> DescendantRebaser<'settings, 'repo> {
|
||||||
let replacements = hashmap! { old_parent_id => new_parent_ids};
|
|
||||||
let old_commits_expression =
|
let old_commits_expression =
|
||||||
RevsetExpression::commits(replacements.keys().cloned().collect());
|
RevsetExpression::commits(replacements.keys().cloned().collect());
|
||||||
let new_commits_expression =
|
let new_commits_expression =
|
||||||
|
|
|
@ -19,6 +19,7 @@ use jujutsu_lib::repo_path::RepoPath;
|
||||||
use jujutsu_lib::rewrite::{DescendantRebaser, RebasedDescendant};
|
use jujutsu_lib::rewrite::{DescendantRebaser, RebasedDescendant};
|
||||||
use jujutsu_lib::testutils;
|
use jujutsu_lib::testutils;
|
||||||
use jujutsu_lib::testutils::CommitGraphBuilder;
|
use jujutsu_lib::testutils::CommitGraphBuilder;
|
||||||
|
use maplit::hashmap;
|
||||||
use test_case::test_case;
|
use test_case::test_case;
|
||||||
|
|
||||||
fn assert_in_place(rebased: Option<RebasedDescendant>, expected_old_commit: &Commit) {
|
fn assert_in_place(rebased: Option<RebasedDescendant>, expected_old_commit: &Commit) {
|
||||||
|
@ -83,8 +84,9 @@ fn test_rebase_descendants_sideways(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit2.id().clone(),
|
hashmap! {
|
||||||
vec![commit6.id().clone()],
|
commit2.id().clone() => vec![commit6.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
let new_commit3 = assert_rebased(rebaser.rebase_next(), &commit3, &[commit6.id().clone()]);
|
let new_commit3 = assert_rebased(rebaser.rebase_next(), &commit3, &[commit6.id().clone()]);
|
||||||
assert_rebased(rebaser.rebase_next(), &commit4, &[new_commit3.id().clone()]);
|
assert_rebased(rebaser.rebase_next(), &commit4, &[new_commit3.id().clone()]);
|
||||||
|
@ -126,8 +128,10 @@ fn test_rebase_descendants_forward(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit2.id().clone(),
|
hashmap! {
|
||||||
vec![commit6.id().clone()],
|
commit2.id().clone() =>
|
||||||
|
vec![commit6.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert_rebased(rebaser.rebase_next(), &commit3, &[commit6.id().clone()]);
|
assert_rebased(rebaser.rebase_next(), &commit3, &[commit6.id().clone()]);
|
||||||
assert_ancestor(rebaser.rebase_next(), &commit4);
|
assert_ancestor(rebaser.rebase_next(), &commit4);
|
||||||
|
@ -162,8 +166,9 @@ fn test_rebase_descendants_backward(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit3.id().clone(),
|
hashmap! {
|
||||||
vec![commit2.id().clone()],
|
commit3.id().clone() => vec![commit2.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert_rebased(rebaser.rebase_next(), &commit4, &[commit2.id().clone()]);
|
assert_rebased(rebaser.rebase_next(), &commit4, &[commit2.id().clone()]);
|
||||||
assert!(rebaser.rebase_next().is_none());
|
assert!(rebaser.rebase_next().is_none());
|
||||||
|
@ -200,8 +205,9 @@ fn test_rebase_descendants_internal_merge(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit2.id().clone(),
|
hashmap! {
|
||||||
vec![commit6.id().clone()],
|
commit2.id().clone() => vec![commit6.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
let new_commit3 = assert_rebased(rebaser.rebase_next(), &commit3, &[commit6.id().clone()]);
|
let new_commit3 = assert_rebased(rebaser.rebase_next(), &commit3, &[commit6.id().clone()]);
|
||||||
let new_commit4 = assert_rebased(rebaser.rebase_next(), &commit4, &[commit6.id().clone()]);
|
let new_commit4 = assert_rebased(rebaser.rebase_next(), &commit4, &[commit6.id().clone()]);
|
||||||
|
@ -245,8 +251,9 @@ fn test_rebase_descendants_external_merge(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit3.id().clone(),
|
hashmap! {
|
||||||
vec![commit6.id().clone()],
|
commit3.id().clone() => vec![commit6.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert_rebased(
|
assert_rebased(
|
||||||
rebaser.rebase_next(),
|
rebaser.rebase_next(),
|
||||||
|
@ -283,8 +290,9 @@ fn test_rebase_descendants_degenerate_merge(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit2.id().clone(),
|
hashmap! {
|
||||||
vec![commit1.id().clone()],
|
commit2.id().clone() => vec![commit1.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert_rebased(rebaser.rebase_next(), &commit4, &[commit3.id().clone()]);
|
assert_rebased(rebaser.rebase_next(), &commit4, &[commit3.id().clone()]);
|
||||||
assert!(rebaser.rebase_next().is_none());
|
assert!(rebaser.rebase_next().is_none());
|
||||||
|
@ -321,8 +329,9 @@ fn test_rebase_descendants_widen_merge(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit5.id().clone(),
|
hashmap! {
|
||||||
vec![commit2.id().clone(), commit3.id().clone()],
|
commit5.id().clone() => vec![commit2.id().clone(), commit3.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
assert_rebased(
|
assert_rebased(
|
||||||
rebaser.rebase_next(),
|
rebaser.rebase_next(),
|
||||||
|
@ -339,6 +348,132 @@ fn test_rebase_descendants_widen_merge(use_git: bool) {
|
||||||
tx.discard();
|
tx.discard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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! {
|
||||||
|
commit2.id().clone() => vec![commit6.id().clone()],
|
||||||
|
commit4.id().clone() => vec![commit6.id().clone()],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_rebased(rebaser.rebase_next(), &commit3, &[commit6.id().clone()]);
|
||||||
|
assert_rebased(rebaser.rebase_next(), &commit5, &[commit6.id().clone()]);
|
||||||
|
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! {
|
||||||
|
commit2.id().clone() => vec![commit4.id().clone()],
|
||||||
|
commit4.id().clone() => vec![commit2.id().clone()],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_rebased(rebaser.rebase_next(), &commit3, &[commit4.id().clone()]);
|
||||||
|
assert_rebased(rebaser.rebase_next(), &commit5, &[commit2.id().clone()]);
|
||||||
|
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! {
|
||||||
|
commit2.id().clone() => vec![commit4.id().clone()],
|
||||||
|
commit6.id().clone() => vec![commit3.id().clone()],
|
||||||
|
},
|
||||||
|
);
|
||||||
|
assert_ancestor(rebaser.rebase_next(), &commit3);
|
||||||
|
assert_ancestor(rebaser.rebase_next(), &commit4);
|
||||||
|
assert_in_place(rebaser.rebase_next(), &commit5);
|
||||||
|
assert_rebased(rebaser.rebase_next(), &commit7, &[commit3.id().clone()]);
|
||||||
|
assert_rebased(rebaser.rebase_next(), &commit8, &[commit4.id().clone()]);
|
||||||
|
assert!(rebaser.rebase_next().is_none());
|
||||||
|
assert_eq!(rebaser.rebased().len(), 2);
|
||||||
|
|
||||||
|
tx.discard();
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case(false ; "local backend")]
|
#[test_case(false ; "local backend")]
|
||||||
#[test_case(true ; "git backend")]
|
#[test_case(true ; "git backend")]
|
||||||
fn test_rebase_descendants_contents(use_git: bool) {
|
fn test_rebase_descendants_contents(use_git: bool) {
|
||||||
|
@ -377,8 +512,9 @@ fn test_rebase_descendants_contents(use_git: bool) {
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
&settings,
|
&settings,
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
commit2.id().clone(),
|
hashmap! {
|
||||||
vec![commit4.id().clone()],
|
commit2.id().clone() => vec![commit4.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
rebaser.rebase_all();
|
rebaser.rebase_all();
|
||||||
let rebased = rebaser.rebased();
|
let rebased = rebaser.rebased();
|
||||||
|
|
|
@ -59,6 +59,7 @@ use jujutsu_lib::transaction::Transaction;
|
||||||
use jujutsu_lib::tree::{Diff, DiffSummary};
|
use jujutsu_lib::tree::{Diff, DiffSummary};
|
||||||
use jujutsu_lib::working_copy::{CheckoutStats, WorkingCopy};
|
use jujutsu_lib::working_copy::{CheckoutStats, WorkingCopy};
|
||||||
use jujutsu_lib::{conflicts, files, git, revset};
|
use jujutsu_lib::{conflicts, files, git, revset};
|
||||||
|
use maplit::hashmap;
|
||||||
use pest::Parser;
|
use pest::Parser;
|
||||||
|
|
||||||
use self::chrono::{FixedOffset, TimeZone, Utc};
|
use self::chrono::{FixedOffset, TimeZone, Utc};
|
||||||
|
@ -2656,8 +2657,9 @@ fn cmd_rebase(
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
ui.settings(),
|
ui.settings(),
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
old_commit.id().clone(),
|
hashmap! {
|
||||||
vec![new_commit.id().clone()],
|
old_commit.id().clone() => vec![new_commit.id().clone()]
|
||||||
|
},
|
||||||
);
|
);
|
||||||
rebaser.rebase_all();
|
rebaser.rebase_all();
|
||||||
let num_rebased = rebaser.rebased().len() + 1;
|
let num_rebased = rebaser.rebased().len() + 1;
|
||||||
|
@ -2673,8 +2675,9 @@ fn cmd_rebase(
|
||||||
let mut rebaser = DescendantRebaser::new(
|
let mut rebaser = DescendantRebaser::new(
|
||||||
ui.settings(),
|
ui.settings(),
|
||||||
tx.mut_repo(),
|
tx.mut_repo(),
|
||||||
old_commit.id().clone(),
|
hashmap! {
|
||||||
old_commit.parent_ids(),
|
old_commit.id().clone() => old_commit.parent_ids()
|
||||||
|
},
|
||||||
);
|
);
|
||||||
rebaser.rebase_all();
|
rebaser.rebase_all();
|
||||||
let num_rebased = rebaser.rebased().len();
|
let num_rebased = rebaser.rebased().len();
|
||||||
|
|
Loading…
Reference in a new issue