diff --git a/CHANGELOG.md b/CHANGELOG.md index 685b50e629..effd42d7c8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -106,6 +106,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 and `jj split any-non-existent-path` inserts an empty commit between the target commit and its parents. +* `jj rebase` now accepts a new `--multi` argument that allows the revset in the + `-d` argument to expand to several revisions. For example, `jj rebase -s B -d + B- -d C` now works even if `B` is a merge commit. + ### Fixed bugs * When sharing the working copy with a Git repo, we used to forget to export diff --git a/src/cli_util.rs b/src/cli_util.rs index 3b5db9946c..26fe2e9901 100644 --- a/src/cli_util.rs +++ b/src/cli_util.rs @@ -1386,15 +1386,26 @@ fn load_revset_aliases( pub fn resolve_base_revs( workspace_command: &WorkspaceCommandHelper, revisions: &[RevisionArg], + multi: bool, ) -> Result, CommandError> { let mut commits = IndexSet::new(); - for revision_str in revisions { - let commit = workspace_command.resolve_single_rev(revision_str)?; - let commit_hash = short_commit_hash(commit.id()); - if !commits.insert(commit) { - return Err(user_error(format!( - r#"More than one revset resolved to revision {commit_hash}"#, - ))); + if multi { + for revset in revisions { + let revisions = workspace_command.resolve_revset(revset)?; + workspace_command.check_non_empty(&revisions)?; + // It's OK if one of the base revs includes a non-rewritable commit + commits.extend(revisions); + } + } else { + // TODO: Change the error resolve_single_rev returns! + for revision_str in revisions { + let commit = workspace_command.resolve_single_rev(revision_str)?; + let commit_hash = short_commit_hash(commit.id()); + if !commits.insert(commit) { + return Err(user_error(format!( + r#"More than one revset resolved to revision {commit_hash}"#, + ))); + } } } diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 7b30c75eac..76d1724506 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -444,6 +444,9 @@ struct NewArgs { /// Parent(s) of the new change #[arg(default_value = "@")] revisions: Vec, + /// Allow revsets expanding to multiple commits in a single argument + #[arg(long)] + multi: bool, /// Ignored (but lets you pass `-r` for consistency with other commands) #[arg(short = 'r', hide = true)] unused_revision: bool, @@ -725,6 +728,9 @@ struct RebaseArgs { /// The revision(s) to rebase onto #[arg(long, short, required = true)] destination: Vec, + /// Allow revsets expanding to multiple commits in a single argument + #[arg(long)] + multi: bool, } /// Apply the reverse of a revision on top of another revision @@ -1994,7 +2000,7 @@ fn cmd_new(ui: &mut Ui, command: &CommandHelper, args: &NewArgs) -> Result<(), C !args.revisions.is_empty(), "expected a non-empty list from clap" ); - let commits = resolve_base_revs(&workspace_command, &args.revisions)? + let commits = resolve_base_revs(&workspace_command, &args.revisions, args.multi)? .into_iter() .collect_vec(); let parent_ids = commits.iter().map(|c| c.id().clone()).collect(); @@ -2685,7 +2691,7 @@ fn cmd_merge(ui: &mut Ui, command: &CommandHelper, args: &NewArgs) -> Result<(), fn cmd_rebase(ui: &mut Ui, command: &CommandHelper, args: &RebaseArgs) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui)?; - let new_parents = resolve_base_revs(&workspace_command, &args.destination)? + let new_parents = resolve_base_revs(&workspace_command, &args.destination, args.multi)? .into_iter() .collect_vec(); if let Some(rev_str) = &args.revision { diff --git a/tests/test_rebase_command.rs b/tests/test_rebase_command.rs index 83059d49be..25cf6e63b1 100644 --- a/tests/test_rebase_command.rs +++ b/tests/test_rebase_command.rs @@ -321,6 +321,17 @@ fn test_rebase_multiple_destinations() { fe2e8e8b50b3 c d370aee184ba b "###); + let stdout = + test_env.jj_cmd_success(&repo_path, &["rebase", "--multi", "-r", "a", "-d", "b|c"]); + insta::assert_snapshot!(stdout, @r###""###); + insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###" + o a + |\ + @ | c + | o b + |/ + o + "###); let stderr = test_env.jj_cmd_failure(&repo_path, &["rebase", "-r", "a", "-d", "b", "-d", "root"]);