diff --git a/cli/src/commands/rebase.rs b/cli/src/commands/rebase.rs index 26dea6cea..b3b9ed2fa 100644 --- a/cli/src/commands/rebase.rs +++ b/cli/src/commands/rebase.rs @@ -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, -) -> Result<(HashSet, usize), CommandError> { +) -> Result<(usize, usize, Vec), 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> = HashMap::new(); + let mut target_commits_internal_parents: HashMap> = 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> = HashMap::new(); + let mut target_commits_external_parents: HashMap> = 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> = + 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 = 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 = 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 { + 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 = 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( diff --git a/cli/tests/test_rebase_command.rs b/cli/tests/test_rebase_command.rs index 0677acdf2..318b9f4eb 100644 --- a/cli/tests/test_rebase_command.rs +++ b/cli/tests/test_rebase_command.rs @@ -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 diff --git a/cli/tests/test_repo_change_report.rs b/cli/tests/test_repo_change_report.rs index ff55df5ea..fc88c7267 100644 --- a/cli/tests/test_repo_change_report.rs +++ b/cli/tests/test_repo_change_report.rs @@ -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.