forked from mirrors/jj
rebase: rewrite rebase_revision
to use transform_descendants
This commit is contained in:
parent
9f4a7318c7
commit
fb7c91ffa8
3 changed files with 57 additions and 163 deletions
|
@ -20,7 +20,6 @@ use std::sync::Arc;
|
|||
use clap::ArgGroup;
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
use jj_lib::backend::CommitId;
|
||||
use jj_lib::commit::{Commit, CommitIteratorExt};
|
||||
use jj_lib::object_id::ObjectId;
|
||||
use jj_lib::repo::{ReadonlyRepo, Repo};
|
||||
|
@ -356,96 +355,48 @@ fn rebase_revision(
|
|||
new_parents: &[Commit],
|
||||
rev_arg: &RevisionArg,
|
||||
) -> Result<(), CommandError> {
|
||||
let old_commit = workspace_command.resolve_single_rev(rev_arg)?;
|
||||
workspace_command.check_rewritable([old_commit.id()])?;
|
||||
if new_parents.contains(&old_commit) {
|
||||
let to_rebase_commit = workspace_command.resolve_single_rev(rev_arg)?;
|
||||
workspace_command.check_rewritable([to_rebase_commit.id()])?;
|
||||
if new_parents.contains(&to_rebase_commit) {
|
||||
return Err(user_error(format!(
|
||||
"Cannot rebase {} onto itself",
|
||||
short_commit_hash(old_commit.id()),
|
||||
short_commit_hash(to_rebase_commit.id()),
|
||||
)));
|
||||
}
|
||||
|
||||
let children_expression = RevsetExpression::commit(old_commit.id().clone()).children();
|
||||
let child_commits: Vec<_> = children_expression
|
||||
.evaluate_programmatic(workspace_command.repo().as_ref())
|
||||
.unwrap()
|
||||
.iter()
|
||||
.commits(workspace_command.repo().store())
|
||||
.try_collect()?;
|
||||
// Currently, immutable commits are defined so that a child of a rewriteable
|
||||
// commit is always rewriteable.
|
||||
debug_assert!(workspace_command
|
||||
.check_rewritable(child_commits.iter().ids())
|
||||
.is_ok());
|
||||
|
||||
// First, rebase the children of `old_commit`.
|
||||
let mut tx = workspace_command.start_transaction();
|
||||
let mut rebased_commit_ids = HashMap::new();
|
||||
for child_commit in child_commits {
|
||||
let new_child_parent_ids: Vec<CommitId> = child_commit
|
||||
.parents()
|
||||
.iter()
|
||||
.flat_map(|c| {
|
||||
if c == &old_commit {
|
||||
old_commit.parent_ids().to_vec()
|
||||
} else {
|
||||
[c.id().clone()].to_vec()
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
||||
// Some of the new parents may be ancestors of others as in
|
||||
// `test_rebase_single_revision`.
|
||||
let new_child_parents_expression = RevsetExpression::commits(new_child_parent_ids.clone())
|
||||
.minus(
|
||||
&RevsetExpression::commits(new_child_parent_ids.clone())
|
||||
.parents()
|
||||
.ancestors(),
|
||||
);
|
||||
let new_child_parents = new_child_parents_expression
|
||||
.evaluate_programmatic(tx.base_repo().as_ref())
|
||||
.unwrap()
|
||||
.iter()
|
||||
.collect_vec();
|
||||
|
||||
rebased_commit_ids.insert(
|
||||
child_commit.id().clone(),
|
||||
rebase_commit(settings, tx.mut_repo(), child_commit, new_child_parents)?
|
||||
.id()
|
||||
.clone(),
|
||||
);
|
||||
}
|
||||
// Now, rebase the descendants of the children.
|
||||
// First, rebase the descendants of `to_rebase_commit`.
|
||||
// TODO(ilyagr): Consider making it possible for these descendants to become
|
||||
// emptied, like --skip_empty. This would require writing careful tests.
|
||||
rebased_commit_ids.extend(tx.mut_repo().rebase_descendants_return_map(settings)?);
|
||||
tx.mut_repo().transform_descendants(
|
||||
settings,
|
||||
vec![to_rebase_commit.id().clone()],
|
||||
|mut rewriter| {
|
||||
let old_commit = rewriter.old_commit();
|
||||
let old_commit_id = old_commit.id().clone();
|
||||
|
||||
// Replace references to `to_rebase_commit` with its parents.
|
||||
if old_commit.parent_ids().contains(to_rebase_commit.id()) {
|
||||
rewriter.replace_parent(to_rebase_commit.id(), to_rebase_commit.parent_ids());
|
||||
rewriter.simplify_ancestor_merge();
|
||||
}
|
||||
if rewriter.parents_changed() {
|
||||
let builder = rewriter.rebase(settings)?;
|
||||
let commit = builder.write()?;
|
||||
rebased_commit_ids.insert(old_commit_id, commit.id().clone());
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
)?;
|
||||
|
||||
let num_rebased_descendants = rebased_commit_ids.len();
|
||||
|
||||
// We now update `new_parents` to account for the rebase of all of
|
||||
// `old_commit`'s descendants. Even if some of the original `new_parents`
|
||||
// were descendants of `old_commit`, this will no longer be the case after
|
||||
// `to_rebase_commit`'s descendants. Even if some of the original `new_parents`
|
||||
// were descendants of `to_rebase_commit`, this will no longer be the case after
|
||||
// the update.
|
||||
//
|
||||
// To make the update simpler, we assume that each commit was rewritten only
|
||||
// once; we don't have a situation where both `(A,B)` and `(B,C)` are in
|
||||
// `rebased_commit_ids`.
|
||||
//
|
||||
// TODO(BUG #2650): There is something wrong with this assumption, the next TODO
|
||||
// seems to be a little optimistic. See the panicked test in
|
||||
// `test_rebase_with_child_and_descendant_bug_2600`.
|
||||
//
|
||||
// TODO(ilyagr): This assumption relies on the fact that, after
|
||||
// `rebase_descendants`, a descendant of `old_commit` cannot also be a
|
||||
// direct child of `old_commit`. This fact will likely change, see
|
||||
// https://github.com/martinvonz/jj/issues/2600. So, the code needs to be
|
||||
// updated before that happens. This would also affect
|
||||
// `test_rebase_with_child_and_descendant_bug_2600`.
|
||||
//
|
||||
// The issue is that if a child and a descendant of `old_commit` were the
|
||||
// same commit (call it `Q`), it would be rebased first by `rebase_commit`
|
||||
// above, and then the result would be rebased again by
|
||||
// `rebase_descendants_return_map`. Then, if we were trying to rebase
|
||||
// `old_commit` onto `Q`, new_parents would only account for one of these.
|
||||
let new_parents = new_parents
|
||||
.iter()
|
||||
.map(|new_parent| {
|
||||
|
@ -456,20 +407,20 @@ fn rebase_revision(
|
|||
})
|
||||
.collect();
|
||||
|
||||
let tx_description = format!("rebase commit {}", old_commit.id().hex());
|
||||
// Finally, it's safe to rebase `old_commit`. We can skip rebasing if it is
|
||||
// already a child of `new_parents`. Otherwise, at this point, it should no
|
||||
// longer have any children; they have all been rebased and the originals
|
||||
let tx_description = format!("rebase commit {}", to_rebase_commit.id().hex());
|
||||
// Finally, it's safe to rebase `to_rebase_commit`. We can skip rebasing if it
|
||||
// is already a child of `new_parents`. Otherwise, at this point, it should
|
||||
// no longer have any children; they have all been rebased and the originals
|
||||
// have been abandoned.
|
||||
let skipped_commit_rebase = if old_commit.parent_ids() == new_parents {
|
||||
let skipped_commit_rebase = if to_rebase_commit.parent_ids() == new_parents {
|
||||
if let Some(mut formatter) = ui.status_formatter() {
|
||||
write!(formatter, "Skipping rebase of commit ")?;
|
||||
tx.write_commit_summary(formatter.as_mut(), &old_commit)?;
|
||||
tx.write_commit_summary(formatter.as_mut(), &to_rebase_commit)?;
|
||||
writeln!(formatter)?;
|
||||
}
|
||||
true
|
||||
} else {
|
||||
rebase_commit(settings, tx.mut_repo(), old_commit, new_parents)?;
|
||||
rebase_commit(settings, tx.mut_repo(), to_rebase_commit, new_parents)?;
|
||||
debug_assert_eq!(tx.mut_repo().rebase_descendants(settings)?, 0);
|
||||
false
|
||||
};
|
||||
|
|
|
@ -227,6 +227,7 @@ impl TestEnvironment {
|
|||
/// Run a `jj` command, check that it failed with code 101, and return its
|
||||
/// stderr
|
||||
#[must_use]
|
||||
#[allow(dead_code)]
|
||||
pub fn jj_cmd_panic(&self, current_dir: &Path, args: &[&str]) -> String {
|
||||
let assert = self.jj_cmd(current_dir, args).assert().code(101).stdout("");
|
||||
self.normalize_output(&get_stderr_string(&assert))
|
||||
|
|
|
@ -310,18 +310,18 @@ fn test_rebase_single_revision() {
|
|||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Also rebased 1 descendant commits onto parent of rebased commit
|
||||
Working copy now at: vruxwmqv bf87078f d | d
|
||||
Parent commit : zsuskuln d370aee1 b | b
|
||||
Working copy now at: vruxwmqv ad122686 d | d
|
||||
Parent commit : rlvkpnrz 2443ea76 a | a
|
||||
Parent commit : zsuskuln d370aee1 b | b
|
||||
Added 0 files, modified 0 files, removed 1 files
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
◉ c
|
||||
│ @ d
|
||||
│ ├─╮
|
||||
│ │ ◉ a
|
||||
│ │ ◉ b
|
||||
├───╯
|
||||
│ ◉ b
|
||||
│ ◉ a
|
||||
├─╯
|
||||
◉
|
||||
"###);
|
||||
|
@ -354,17 +354,17 @@ fn test_rebase_single_revision_merge_parent() {
|
|||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Also rebased 1 descendant commits onto parent of rebased commit
|
||||
Working copy now at: vruxwmqv c62d0789 d | d
|
||||
Parent commit : zsuskuln d370aee1 b | b
|
||||
Working copy now at: vruxwmqv a37531e8 d | d
|
||||
Parent commit : rlvkpnrz 2443ea76 a | a
|
||||
Parent commit : zsuskuln d370aee1 b | b
|
||||
Added 0 files, modified 0 files, removed 1 files
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
◉ c
|
||||
│ @ d
|
||||
╭─┤
|
||||
◉ │ a
|
||||
│ ◉ b
|
||||
◉ │ a
|
||||
├─╯
|
||||
◉
|
||||
"###);
|
||||
|
@ -849,7 +849,7 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
|
|||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Skipping rebase of commit rlvkpnrz 0c61db1b base | base
|
||||
Rebased 4 descendant commits onto parent of commit
|
||||
Rebased 3 descendant commits onto parent of commit
|
||||
Working copy now at: vruxwmqv 57aaa944 c | c
|
||||
Parent commit : royxmykx c8495a71 b | b
|
||||
Added 0 files, modified 0 files, removed 1 files
|
||||
|
@ -872,74 +872,17 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
|
|||
◉
|
||||
"###);
|
||||
|
||||
// TODO(#2650) !!!!! The panic here is a BUG !!!
|
||||
// This tests the algorithm for rebasing onto descendants. The result should be
|
||||
// simplified if and only if it's simplified in the above case.
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
let stderr = test_env.jj_cmd_panic(&repo_path, &["rebase", "-r", "base", "-d", "b"]);
|
||||
assert!(stderr.contains("graph has cycle"));
|
||||
// // At time of writing:
|
||||
// insta::assert_snapshot!(stderr, @r###"
|
||||
// thread 'main' panicked at lib/src/dag_walk.rs:113:13:
|
||||
// graph has cycle
|
||||
// stack backtrace:
|
||||
// 0: rust_begin_unwind
|
||||
// at
|
||||
// /rustc/6cf088810f66fff15d05bf7135c5f5888b7c93b4/library/std/src/panicking.rs:
|
||||
// 645:5 1: core::panicking::panic_fmt
|
||||
// at
|
||||
// /rustc/6cf088810f66fff15d05bf7135c5f5888b7c93b4/library/core/src/panicking.
|
||||
// rs:72:14 2: jj_lib::dag_walk::topo_order_forward_ok
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/lib/src/dag_walk.rs:113:13
|
||||
// 3: jj_lib::dag_walk::topo_order_reverse_ok
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/lib/src/dag_walk.rs:160:22
|
||||
// 4: jj_lib::dag_walk::topo_order_reverse
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/lib/src/dag_walk.rs:142:5
|
||||
// 5: jj_lib::rewrite::DescendantRebaser::new
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/lib/src/rewrite.rs:306:24
|
||||
// 6: jj_lib::repo::MutableRepo::create_descendant_rebaser
|
||||
// at /usr/local/google/home/ilyagr/dev/jj/lib/src/repo.rs:844:9
|
||||
// 7: jj_lib::repo::MutableRepo::rebase_descendants_return_rebaser
|
||||
// at /usr/local/google/home/ilyagr/dev/jj/lib/src/repo.rs:861:27
|
||||
// 8: jj_lib::repo::MutableRepo::rebase_descendants_with_options
|
||||
// at /usr/local/google/home/ilyagr/dev/jj/lib/src/repo.rs:872:12
|
||||
// 9: jj_lib::repo::MutableRepo::rebase_descendants
|
||||
// at /usr/local/google/home/ilyagr/dev/jj/lib/src/repo.rs:878:9
|
||||
// 10: jj_cli::commands::rebase::rebase_revision
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/cli/src/commands/rebase.rs:420:22
|
||||
// 11: jj_cli::commands::rebase::cmd_rebase
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/cli/src/commands/rebase.rs:197:9
|
||||
// 12: jj_cli::commands::run_command
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/cli/src/commands/mod.rs:187:39 13:
|
||||
// core::ops::function::FnOnce::call_once at
|
||||
// /rustc/6cf088810f66fff15d05bf7135c5f5888b7c93b4/library/core/src/ops/
|
||||
// function.rs:250:5 14: core::ops::function::FnOnce::call_once{{vtable.
|
||||
// shim}} at
|
||||
// /rustc/6cf088810f66fff15d05bf7135c5f5888b7c93b4/library/core/src/ops/
|
||||
// function.rs:250:5 15: <alloc::boxed::Box<F,A> as
|
||||
// core::ops::function::FnOnce<Args>>::call_once at
|
||||
// /rustc/6cf088810f66fff15d05bf7135c5f5888b7c93b4/library/alloc/src/boxed.rs:
|
||||
// 2007:9 16: jj_cli::cli_util::CliRunner::run_internal
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/cli/src/cli_util.rs:2867:9
|
||||
// 17: jj_cli::cli_util::CliRunner::run
|
||||
// at
|
||||
// /usr/local/google/home/ilyagr/dev/jj/cli/src/cli_util.rs:2884:22
|
||||
// 18: jj::main
|
||||
// at /usr/local/google/home/ilyagr/dev/jj/cli/src/main.rs:18:5
|
||||
// 19: core::ops::function::FnOnce::call_once
|
||||
// at
|
||||
// /rustc/6cf088810f66fff15d05bf7135c5f5888b7c93b4/library/core/src/ops/
|
||||
// function.rs:250:5 note: Some details are omitted, run with
|
||||
// `RUST_BACKTRACE=full` for a verbose backtrace. "###);
|
||||
//
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["rebase", "-r", "base", "-d", "b"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Also rebased 3 descendant commits onto parent of rebased commit
|
||||
Working copy now at: vruxwmqv a72f0141 c | c
|
||||
Parent commit : royxmykx 7033e775 b | b
|
||||
Added 0 files, modified 0 files, removed 1 files
|
||||
"###);
|
||||
// Unsimlified ancestry would look like
|
||||
// @ c
|
||||
// │ ◉ base
|
||||
|
@ -950,12 +893,11 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
|
|||
// ├─╯
|
||||
// ◉
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ c
|
||||
◉ b
|
||||
├─╮
|
||||
│ ◉ a
|
||||
├─╯
|
||||
◉ base
|
||||
│ @ c
|
||||
├─╯
|
||||
◉ b
|
||||
◉ a
|
||||
◉
|
||||
"###);
|
||||
|
||||
|
@ -965,7 +907,7 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
|
|||
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["rebase", "-r", "base", "-d", "a"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Also rebased 4 descendant commits onto parent of rebased commit
|
||||
Also rebased 3 descendant commits onto parent of rebased commit
|
||||
Working copy now at: vruxwmqv 0b91d0eb c | c
|
||||
Parent commit : royxmykx fb944989 b | b
|
||||
Added 0 files, modified 0 files, removed 1 files
|
||||
|
|
Loading…
Reference in a new issue