rebase: add move_commits function to perform rebasing

The `move_commits` function accepts a set of target commits to shift to
a new location given by `new_parents` and `new_children`. The roots of
the target set will be reparented onto `new_parents`. `new_children`
will then be reparented onto the heads of the target set.

The commits will be rebased in reverse topological order based on the
new set of parents of each commit, which avoids the need for multiple
sets of rebase operations.
This commit is contained in:
Benjamin Tan 2024-03-29 21:32:55 +08:00
parent 492dd99ba5
commit 714bc0a9e6
3 changed files with 420 additions and 216 deletions

View file

@ -18,12 +18,13 @@ use std::io::Write;
use std::sync::Arc;
use clap::ArgGroup;
use indexmap::IndexSet;
use indexmap::{IndexMap, IndexSet};
use itertools::Itertools;
use jj_lib::backend::CommitId;
use jj_lib::commit::{Commit, CommitIteratorExt};
use jj_lib::dag_walk;
use jj_lib::object_id::ObjectId;
use jj_lib::repo::{ReadonlyRepo, Repo};
use jj_lib::repo::{MutableRepo, ReadonlyRepo, Repo};
use jj_lib::revset::{RevsetExpression, RevsetIteratorExt};
use jj_lib::rewrite::{rebase_commit_with_options, CommitRewriter, EmptyBehaviour, RebaseOptions};
use jj_lib::settings::UserSettings;
@ -376,6 +377,29 @@ fn rebase_revisions(
}
}
move_commits_transaction(
ui,
settings,
workspace_command,
&new_parents.iter().ids().cloned().collect_vec(),
&[],
target_commits,
)
}
/// Wraps `move_commits` in a transaction.
fn move_commits_transaction(
ui: &mut Ui,
settings: &UserSettings,
workspace_command: &mut WorkspaceCommandHelper,
new_parent_ids: &[CommitId],
new_children: &[Commit],
target_commits: &[Commit],
) -> Result<(), CommandError> {
if target_commits.is_empty() {
return Ok(());
}
let mut tx = workspace_command.start_transaction();
let tx_description = if target_commits.len() == 1 {
format!("rebase commit {}", target_commits[0].id().hex())
@ -387,48 +411,12 @@ fn rebase_revisions(
)
};
let target_commit_ids: HashSet<_> = target_commits.iter().ids().cloned().collect();
// First, rebase the descendants of `target_commits`.
let (target_roots, num_rebased_descendants) =
extract_commits(&mut tx, settings, target_commits, &target_commit_ids)?;
// We now update `new_parents` to account for the rebase of all of
// `target_commits`'s descendants. Even if some of the original `new_parents`
// were descendants of `target_commits`, this will no longer be the case after
// the update.
let new_parents = tx
.mut_repo()
.new_parents(new_parents.iter().ids().cloned().collect_vec());
let mut skipped_commits = Vec::new();
// At this point, all commits in the target set will only have other commits in
// the set as their ancestors. We can now safely rebase `target_commits` onto
// the `new_parents`, by updating the roots' parents and rebasing its
// descendants.
tx.mut_repo().transform_descendants(
let (num_rebased_targets, num_rebased_descendants, skipped_commits) = move_commits(
settings,
target_roots.iter().cloned().collect_vec(),
|mut rewriter| {
let old_commit = rewriter.old_commit();
let old_commit_id = old_commit.id().clone();
if target_roots.contains(&old_commit_id) {
rewriter.set_new_parents(new_parents.clone());
}
if rewriter.parents_changed() {
rewriter.rebase(settings)?.write()?;
} else {
// Only include the commit in the list of skipped commits if it wasn't
// previously rewritten. Commits in the target set could have previously been
// rewritten in `extract_commits` if they are not a root, and some of its
// parents are not part of the target set.
if target_commit_ids.contains(&old_commit_id) {
skipped_commits.push(rewriter.old_commit().clone());
}
}
Ok(())
},
tx.mut_repo(),
new_parent_ids,
new_children,
target_commits,
)?;
if let Some(mut fmt) = ui.status_formatter() {
@ -439,42 +427,46 @@ fn rebase_revisions(
skipped_commits.iter(),
)?;
}
let num_rebased = target_commits.len() - skipped_commits.len();
if num_rebased > 0 {
writeln!(fmt, "Rebased {num_rebased} commits onto destination")?;
}
if num_rebased_descendants > 0 {
if num_rebased_targets > 0 {
writeln!(
fmt,
"Rebased {num_rebased_descendants} descendant commits onto parents of rebased \
commits"
"Rebased {num_rebased_targets} commits onto destination"
)?;
}
if num_rebased_descendants > 0 {
writeln!(fmt, "Rebased {num_rebased_descendants} descendant commits")?;
}
}
if tx.mut_repo().has_changes() {
tx.finish(ui, tx_description)
} else {
Ok(()) // Do not print "Nothing changed."
}
tx.finish(ui, tx_description)
}
/// Extracts `target_commits` from the graph by rebasing its descendants onto
/// its parents. This assumes that `target_commits` can be rewritten.
/// Moves `target_commits` from their current location to a new location in the
/// graph, given by the set of `new_parent_ids` and `new_children`.
/// The roots of `target_commits` are rebased onto the new parents, while the
/// new children are rebased onto the heads of `target_commits`.
/// This assumes that `target_commits` and `new_children` can be rewritten, and
/// there will be no cycles in the resulting graph.
/// `target_commits` should be in reverse topological order.
/// Returns a tuple of the commit IDs of the roots of the `target_commits` set
/// and the number of rebased descendants which were not in the set.
fn extract_commits(
tx: &mut WorkspaceCommandTransaction,
fn move_commits(
settings: &UserSettings,
mut_repo: &mut MutableRepo,
new_parent_ids: &[CommitId],
new_children: &[Commit],
target_commits: &[Commit],
target_commit_ids: &HashSet<CommitId>,
) -> Result<(HashSet<CommitId>, usize), CommandError> {
) -> Result<(usize, usize, Vec<Commit>), CommandError> {
if target_commits.is_empty() {
return Ok((0, 0, vec![]));
}
let target_commit_ids: HashSet<_> = target_commits.iter().ids().cloned().collect();
let connected_target_commits: Vec<_> =
RevsetExpression::commits(target_commits.iter().ids().cloned().collect_vec())
.connected()
.evaluate_programmatic(tx.base_repo().as_ref())?
.evaluate_programmatic(mut_repo)?
.iter()
.commits(tx.base_repo().store())
.commits(mut_repo.store())
.try_collect()?;
// Commits in the target set should only have other commits in the set as
@ -483,7 +475,7 @@ fn extract_commits(
// If a commit in the set has a parent which is not in the set, but has
// an ancestor which is in the set, then the commit will have that ancestor
// as a parent.
let mut new_target_parents: HashMap<CommitId, Vec<CommitId>> = HashMap::new();
let mut target_commits_internal_parents: HashMap<CommitId, Vec<CommitId>> = HashMap::new();
for commit in connected_target_commits.iter().rev() {
// The roots of the set will not have any parents found in `new_target_parents`,
// and will be stored in `new_target_parents` as an empty vector.
@ -491,16 +483,16 @@ fn extract_commits(
for old_parent in commit.parent_ids() {
if target_commit_ids.contains(old_parent) {
new_parents.push(old_parent.clone());
} else if let Some(parents) = new_target_parents.get(old_parent) {
} else if let Some(parents) = target_commits_internal_parents.get(old_parent) {
new_parents.extend(parents.iter().cloned());
}
}
new_target_parents.insert(commit.id().clone(), new_parents);
target_commits_internal_parents.insert(commit.id().clone(), new_parents);
}
new_target_parents.retain(|id, _| target_commit_ids.contains(id));
target_commits_internal_parents.retain(|id, _| target_commit_ids.contains(id));
// Compute the roots of `target_commits`.
let target_roots: HashSet<_> = new_target_parents
let target_roots: HashSet<_> = target_commits_internal_parents
.iter()
.filter(|(_, parents)| parents.is_empty())
.map(|(commit_id, _)| commit_id.clone())
@ -509,69 +501,275 @@ fn extract_commits(
// If a commit outside the target set has a commit in the target set as a
// parent, then - after the transformation - it should have that commit's
// ancestors which are not in the target set as parents.
let mut new_child_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
let mut target_commits_external_parents: HashMap<CommitId, IndexSet<CommitId>> = HashMap::new();
for commit in target_commits.iter().rev() {
let mut new_parents = IndexSet::new();
for old_parent in commit.parent_ids() {
if let Some(parents) = new_child_parents.get(old_parent) {
if let Some(parents) = target_commits_external_parents.get(old_parent) {
new_parents.extend(parents.iter().cloned());
} else {
new_parents.insert(old_parent.clone());
}
}
new_child_parents.insert(commit.id().clone(), new_parents);
target_commits_external_parents.insert(commit.id().clone(), new_parents);
}
let mut num_rebased_descendants = 0;
// If the new parents include a commit in the target set, replace it with the
// commit's ancestors which are outside the set.
// e.g. `jj rebase -r A --before A`
let new_parent_ids: Vec<_> = new_parent_ids
.iter()
.flat_map(|parent_id| {
if let Some(parent_ids) = target_commits_external_parents.get(parent_id) {
parent_ids.iter().cloned().collect_vec()
} else {
[parent_id.clone()].to_vec()
}
})
.collect();
// TODO(ilyagr): Consider making it possible for these descendants
// to become emptied, like --skip-empty. This would require writing careful
// tests.
tx.mut_repo().transform_descendants(
settings,
target_commits.iter().ids().cloned().collect_vec(),
|mut rewriter| {
let old_commit = rewriter.old_commit();
let old_commit_id = old_commit.id().clone();
// If the new children include a commit in the target set, replace it with the
// commit's descendants which are outside the set.
// e.g. `jj rebase -r A --after A`
let new_children: Vec<_> = if new_children
.iter()
.any(|child| target_commit_ids.contains(child.id()))
{
let target_commits_descendants: Vec<_> =
RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
.union(
&RevsetExpression::commits(target_commit_ids.iter().cloned().collect_vec())
.children(),
)
.evaluate_programmatic(mut_repo)?
.iter()
.commits(mut_repo.store())
.try_collect()?;
// For all commits in the target set, compute its transitive descendant commits
// which are outside of the target set by up to 1 generation.
let mut target_commit_external_descendants: HashMap<CommitId, IndexSet<Commit>> =
HashMap::new();
// Iterate through all descendants of the target set, going through children
// before parents.
for commit in target_commits_descendants.iter() {
if !target_commit_external_descendants.contains_key(commit.id()) {
let children = if target_commit_ids.contains(commit.id()) {
IndexSet::new()
} else {
IndexSet::from([commit.clone()])
};
target_commit_external_descendants.insert(commit.id().clone(), children);
}
let children = target_commit_external_descendants
.get(commit.id())
.unwrap()
.iter()
.cloned()
.collect_vec();
for parent_id in commit.parent_ids() {
if target_commit_ids.contains(parent_id) {
if let Some(target_children) =
target_commit_external_descendants.get_mut(parent_id)
{
target_children.extend(children.iter().cloned());
} else {
target_commit_external_descendants
.insert(parent_id.clone(), children.iter().cloned().collect());
}
};
}
}
new_children
.iter()
.flat_map(|child| {
if let Some(children) = target_commit_external_descendants.get(child.id()) {
children.iter().cloned().collect_vec()
} else {
[child.clone()].to_vec()
}
})
.collect()
} else {
new_children.to_vec()
};
// Compute the parents of the new children, which will include the heads of the
// target set.
let new_children_parents: HashMap<_, _> = if !new_children.is_empty() {
// Compute the heads of the target set, which will be used as the parents of
// `new_children`.
let mut target_heads: HashSet<CommitId> = HashSet::new();
for commit in connected_target_commits.iter().rev() {
target_heads.insert(commit.id().clone());
for old_parent in commit.parent_ids() {
target_heads.remove(old_parent);
}
}
let target_heads = connected_target_commits
.iter()
.rev()
.filter(|commit| {
target_heads.contains(commit.id()) && target_commit_ids.contains(commit.id())
})
.map(|commit| commit.id().clone())
.collect_vec();
new_children
.iter()
.map(|child_commit| {
let mut new_child_parent_ids: IndexSet<_> = child_commit
.parent_ids()
.iter()
// Replace target commits with their parents outside the target set.
.flat_map(|id| {
if let Some(parents) = target_commits_external_parents.get(id) {
parents.iter().cloned().collect_vec()
} else {
[id.clone()].to_vec()
}
})
// Exclude any of the new parents of the target commits, since we are
// "inserting" the target commits in between the new parents and the new
// children.
.filter(|id| {
!new_parent_ids
.iter()
.any(|new_parent_id| new_parent_id == id)
})
.collect();
// Add `target_heads` as parents of the new child commit.
new_child_parent_ids.extend(target_heads.clone());
(
child_commit.id().clone(),
new_child_parent_ids.iter().cloned().collect_vec(),
)
})
.collect()
} else {
HashMap::new()
};
// Compute the set of commits to visit, which includes the target commits, the
// new children commits (if any), and their descendants.
let mut roots = target_roots.iter().cloned().collect_vec();
roots.extend(new_children.iter().ids().cloned());
let to_visit_expression = RevsetExpression::commits(roots).descendants();
let to_visit: Vec<_> = to_visit_expression
.evaluate_programmatic(mut_repo)?
.iter()
.commits(mut_repo.store())
.try_collect()?;
let to_visit_commits: IndexMap<_, _> = to_visit
.into_iter()
.map(|commit| (commit.id().clone(), commit))
.collect();
let to_visit_commits_new_parents: HashMap<_, _> = to_visit_commits
.iter()
.map(|(commit_id, commit)| {
let new_parents =
// New child of the rebased target commits.
if let Some(new_child_parents) = new_children_parents.get(commit_id) {
new_child_parents.clone()
}
// Commits in the target set should persist only rebased parents from the target
// sets.
if let Some(new_parents) = new_target_parents.get(&old_commit_id) {
// If the commit does not have any parents in the target set, its parents
// will be persisted since it is one of the roots of the set.
if !new_parents.is_empty() {
rewriter.set_new_rewritten_parents(new_parents.clone());
else if let Some(target_commit_parents) =
target_commits_internal_parents.get(commit_id)
{
// If the commit does not have any parents in the target set, it is one of the
// commits in the root set, and should be rebased onto the new destination.
if target_commit_parents.is_empty() {
new_parent_ids.clone()
} else {
target_commit_parents.clone()
}
}
// Commits outside the target set should have references to commits inside the set
// replaced.
else if rewriter
.old_commit()
else if commit
.parent_ids()
.iter()
.any(|id| new_child_parents.contains_key(id))
.any(|id| target_commits_external_parents.contains_key(id))
{
let mut new_parents = vec![];
for parent in rewriter.old_commit().parent_ids() {
if let Some(parents) = new_child_parents.get(parent) {
for parent in commit.parent_ids() {
if let Some(parents) = target_commits_external_parents.get(parent) {
new_parents.extend(parents.iter().cloned());
} else {
new_parents.push(parent.clone());
}
}
rewriter.set_new_rewritten_parents(new_parents);
}
if rewriter.parents_changed() {
rewriter.rebase(settings)?.write()?;
if !target_commit_ids.contains(&old_commit_id) {
num_rebased_descendants += 1;
}
}
Ok(())
},
)?;
new_parents
} else {
commit.parent_ids().iter().cloned().collect_vec()
};
Ok((target_roots, num_rebased_descendants))
(commit_id.clone(), new_parents)
})
.collect();
// Re-compute the order of commits to visit, such that each commit's new parents
// must be visited first.
let mut visited: HashSet<CommitId> = HashSet::new();
let mut to_visit = dag_walk::topo_order_reverse(
to_visit_commits.keys().cloned().collect_vec(),
|commit_id| commit_id.clone(),
|commit_id| -> Vec<CommitId> {
visited.insert(commit_id.clone());
to_visit_commits_new_parents
.get(commit_id)
.cloned()
.unwrap()
.iter()
// Only add parents which are in the set to be visited and have not already been
// visited.
.filter(|&id| to_visit_commits.contains_key(id) && !visited.contains(id))
.cloned()
.collect()
},
);
let mut skipped_commits: Vec<Commit> = vec![];
let mut num_rebased_targets = 0;
let mut num_rebased_descendants = 0;
// Rebase each commit onto its new parents in the reverse topological order
// computed above.
// TODO(ilyagr): Consider making it possible for descendants of the target set
// to become emptied, like --skip-empty. This would require writing careful
// tests.
while let Some(old_commit_id) = to_visit.pop() {
let old_commit = to_visit_commits.get(&old_commit_id).unwrap();
let parent_ids = to_visit_commits_new_parents
.get(&old_commit_id)
.cloned()
.unwrap();
let new_parent_ids = mut_repo.new_parents(parent_ids);
let rewriter = CommitRewriter::new(mut_repo, old_commit.clone(), new_parent_ids);
if rewriter.parents_changed() {
rewriter.rebase(settings)?.write()?;
if target_commit_ids.contains(&old_commit_id) {
num_rebased_targets += 1;
} else {
num_rebased_descendants += 1;
}
} else {
skipped_commits.push(old_commit.clone());
}
}
mut_repo.update_rewritten_references(settings)?;
Ok((
num_rebased_targets,
num_rebased_descendants,
skipped_commits,
))
}
fn check_rebase_destinations(

View file

@ -288,16 +288,17 @@ fn test_rebase_single_revision() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 2 descendant commits onto parents of rebased commits
Rebased 2 descendant commits
Working copy now at: znkkpsqq 2668ffbe e | e
Parent commit : vruxwmqv 7b370c85 d | d
Added 0 files, modified 0 files, removed 1 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
c
@ e
d
@ e
d
c
b
a
@ -311,19 +312,19 @@ fn test_rebase_single_revision() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 1 descendant commits onto parents of rebased commits
Rebased 1 descendant commits
Working copy now at: znkkpsqq ed210c15 e | e
Parent commit : zsuskuln 1394f625 b | b
Parent commit : royxmykx c0cb3a0b c | c
Added 0 files, modified 0 files, removed 1 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
d
@ e
c
b
@ e
c
b
d
a
@ -357,17 +358,18 @@ fn test_rebase_single_revision_merge_parent() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 1 descendant commits onto parents of rebased commits
Rebased 1 descendant commits
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
@ d
b
c
a
@ -412,24 +414,24 @@ fn test_rebase_multiple_revisions() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 2 commits onto destination
Rebased 4 descendant commits onto parents of rebased commits
Rebased 4 descendant commits
Working copy now at: xznxytkn 016685dc i | i
Parent commit : kmkuslsw e04d3932 f | f
Added 0 files, modified 0 files, removed 2 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
e
c
@ i
h
g
@ i
h
g
f
d
b
f
d
b
e
c
a
@ -444,22 +446,23 @@ fn test_rebase_multiple_revisions() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 2 commits onto destination
Rebased 4 descendant commits onto parents of rebased commits
Rebased 4 descendant commits
Working copy now at: xznxytkn 94538385 i | i
Parent commit : kmkuslsw dae8d293 f | f
Added 0 files, modified 0 files, removed 2 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
c
b
@ i
h
g
@ i
h
g
f
c
b
f
e
d
e
d
a
@ -473,24 +476,24 @@ fn test_rebase_multiple_revisions() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 3 commits onto destination
Rebased 2 descendant commits onto parents of rebased commits
Rebased 2 descendant commits
Working copy now at: xznxytkn 1868ded4 i | i
Parent commit : royxmykx 7e4fbf4f c | c
Parent commit : vruxwmqv 4cc44fbf d | d
Added 0 files, modified 0 files, removed 2 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
g
f
e
@ i
h
d
c
b
@ i
h
d
c
b
g
f
e
a
@ -508,25 +511,25 @@ fn test_rebase_multiple_revisions() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 3 commits onto destination
Rebased 3 descendant commits onto parents of rebased commits
Rebased 3 descendant commits
Working copy now at: xznxytkn 9cfd1635 i | i
Parent commit : royxmykx 7e4fbf4f c | c
Parent commit : znkkpsqq ecf9a1d5 e | e
Added 0 files, modified 0 files, removed 2 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
h
f
d
@ i
g
e
c
b
@ i
g
e
c
h
f
d
b
a
"###);
@ -537,17 +540,17 @@ fn test_rebase_multiple_revisions() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 2 commits onto destination
Rebased 4 descendant commits onto parents of rebased commits
Rebased 4 descendant commits
Working copy now at: xznxytkn 5d911e5c i | i
Parent commit : kmkuslsw d1bfda8c f | f
Added 0 files, modified 0 files, removed 2 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
e
d
@ i
h
g
h
g
e
d
@ i
f
@ -586,18 +589,19 @@ fn test_rebase_revision_onto_descendant() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 3 descendant commits onto parents of rebased commits
Rebased 3 descendant commits
Working copy now at: vruxwmqv bff4a4eb merge | merge
Parent commit : royxmykx c84e900d b | b
Parent commit : zsuskuln d57db87b a | a
Added 0 files, modified 0 files, removed 1 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
base
@ merge
a
b
@ merge
b
base
a
"###);
@ -615,7 +619,7 @@ fn test_rebase_revision_onto_descendant() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 3 descendant commits onto parents of rebased commits
Rebased 3 descendant commits
Working copy now at: vruxwmqv 986b7a49 merge | merge
Parent commit : royxmykx c07c677c b | b
Parent commit : zsuskuln abc90087 a | a
@ -1054,20 +1058,20 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 3 descendant commits onto parents of rebased commits
Rebased 3 descendant commits
Working copy now at: znkkpsqq 45371aaf c | c
Parent commit : vruxwmqv c0a76bf4 b | b
Added 0 files, modified 0 files, removed 1 files
"###);
// The user would expect unsimplified ancestry here.
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
base
@ c
b
a
notroot
@ c
b
a
notroot
base
"###);
@ -1079,14 +1083,14 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 3 descendant commits onto parents of rebased commits
Rebased 3 descendant commits
Working copy now at: znkkpsqq e28fa972 c | c
Parent commit : vruxwmqv 8d0eeb6a b | b
Added 0 files, modified 0 files, removed 1 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
base
@ c
@ c
base
b
@ -1103,17 +1107,18 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 3 descendant commits onto parents of rebased commits
Rebased 3 descendant commits
Working copy now at: znkkpsqq a9da974c c | c
Parent commit : vruxwmqv 0072139c b | b
Added 0 files, modified 0 files, removed 1 files
"###);
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
base
@ c
b
a
@ c
b
base
a
notroot
@ -1136,7 +1141,7 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 2 descendant commits onto parents of rebased commits
Rebased 2 descendant commits
Working copy now at: znkkpsqq 7210b05e c | c
Parent commit : vruxwmqv da3f7511 b | b
Added 0 files, modified 0 files, removed 1 files
@ -1144,11 +1149,11 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
// In this case, it is unclear whether the user would always prefer unsimplified
// ancestry (whether `b` should also be a direct child of the root commit).
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
a
@ c
b
base
notroot
@ c
b
base
notroot
a
"###);
@ -1158,7 +1163,7 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 1 descendant commits onto parents of rebased commits
Rebased 1 descendant commits
Working copy now at: znkkpsqq f280545e c | c
Parent commit : zsuskuln 0a7fb8f6 base | base
Parent commit : royxmykx 86a06598 a | a
@ -1166,13 +1171,13 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
"###);
// The user would expect unsimplified ancestry here.
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
b
@ c
a
base
notroot
@ c
a
base
notroot
b
"###);
@ -1184,7 +1189,7 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 1 descendant commits onto parents of rebased commits
Rebased 1 descendant commits
Working copy now at: znkkpsqq c0a7cd80 c | c
Parent commit : zsuskuln 0a7fb8f6 base | base
Parent commit : royxmykx 86a06598 a | a
@ -1343,6 +1348,7 @@ fn test_rebase_skip_if_on_destination() {
// Skip rebase with -r since commit has no children
insta::assert_snapshot!(stderr, @r###"
Skipping rebase of commit znkkpsqq 92438fc9 d | d
Nothing changed.
"###);
insta::assert_snapshot!(get_long_log_output(&test_env, &repo_path), @r###"
@ f lylxulpl 88f778c5
@ -1363,7 +1369,7 @@ fn test_rebase_skip_if_on_destination() {
// Skip rebase of commit, but rebases children onto destination with -r
insta::assert_snapshot!(stderr, @r###"
Skipping rebase of commit kmkuslsw 48dd9e3f e | e
Rebased 1 descendant commits onto parents of rebased commits
Rebased 1 descendant commits
Working copy now at: lylxulpl 77cb229f f | f
Parent commit : vruxwmqv c41e416e c | c
Added 0 files, modified 0 files, removed 1 files

View file

@ -65,13 +65,13 @@ fn test_report_conflicts() {
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r###"
Rebased 1 commits onto destination
Rebased 2 descendant commits onto parents of rebased commits
Rebased 2 descendant commits
New conflicts appeared in these commits:
rlvkpnrz 262c4c38 (conflict) B
kkmpptxz d1edf578 (conflict) C
rlvkpnrz 262c4c38 (conflict) B
To resolve the conflicts, start by updating to one of the first ones:
jj new rlvkpnrzqnoo
jj new kkmpptxzrspx
jj new rlvkpnrzqnoo
Then use `jj resolve`, or edit the conflict markers in the file directly.
Once the conflicts are resolved, you may want inspect the result with `jj diff`.
Then run `jj squash` to move the resolution into the conflicted commit.