diff --git a/cli/src/commands/squash.rs b/cli/src/commands/squash.rs index 36b3c60c60..995fcb5db5 100644 --- a/cli/src/commands/squash.rs +++ b/cli/src/commands/squash.rs @@ -53,9 +53,9 @@ pub(crate) struct SquashArgs { /// Revision to squash into its parent (default: @) #[arg(long, short)] revision: Option, - /// Revision to squash from (default: @) + /// Revision(s) to squash from (default: @) #[arg(long, conflicts_with = "revision")] - from: Option, + from: Vec, /// Revision to squash into (default: @) #[arg(long, conflicts_with = "revision", visible_alias = "to")] into: Option, @@ -83,11 +83,14 @@ pub(crate) fn cmd_squash( let mut sources: Vec; let destination; - if args.from.is_some() || args.into.is_some() { - sources = workspace_command - .parse_revset(args.from.as_ref().unwrap_or(&RevisionArg::AT))? - .evaluate_to_commits()? - .try_collect()?; + if !args.from.is_empty() || args.into.is_some() { + sources = if args.from.is_empty() { + workspace_command.parse_revset(&RevisionArg::AT)? + } else { + workspace_command.parse_union_revsets(&args.from)? + } + .evaluate_to_commits()? + .try_collect()?; destination = workspace_command.resolve_single_rev(args.into.as_ref().unwrap_or(&RevisionArg::AT))?; if sources.iter().any(|source| source.id() == destination.id()) { @@ -124,7 +127,7 @@ pub(crate) fn cmd_squash( matcher.as_ref(), &diff_selector, description, - args.revision.is_none() && args.from.is_none() && args.into.is_none(), + args.revision.is_none() && args.from.is_empty() && args.into.is_none(), &args.paths, )?; tx.finish(ui, tx_description)?; diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index 70a936cece..bc3b086109 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -1720,7 +1720,7 @@ If a working-copy commit gets abandoned, it will be given a new, empty commit. T ###### **Options:** * `-r`, `--revision ` — Revision to squash into its parent (default: @) -* `--from ` — Revision to squash from (default: @) +* `--from ` — Revision(s) to squash from (default: @) * `--into ` — Revision to squash into (default: @) * `-m`, `--message ` — The description to use for squashed revision (don't open editor) * `-i`, `--interactive` — Interactively choose which parts to squash diff --git a/cli/tests/test_squash_command.rs b/cli/tests/test_squash_command.rs index 0156f88778..9161ca9bec 100644 --- a/cli/tests/test_squash_command.rs +++ b/cli/tests/test_squash_command.rs @@ -629,7 +629,8 @@ fn test_squash_from_multiple() { "###); // Squash a few commits sideways - let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--from=b|c", "--into=d"]); + let (stdout, stderr) = + test_env.jj_cmd_ok(&repo_path, &["squash", "--from=b", "--from=c", "--into=d"]); insta::assert_snapshot!(stdout, @""); insta::assert_snapshot!(stderr, @r###" Rebased 2 descendant commits @@ -690,6 +691,14 @@ fn test_squash_from_multiple() { insta::assert_snapshot!(stdout, @r###" f "###); + + // Empty squash shouldn't crash (could be noop) + let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["squash", "--from=none()"]); + insta::assert_snapshot!(stdout, @""); + insta::assert_snapshot!(stderr, @r###" + Working copy now at: xznxytkn 68d54010 (empty) (no description set) + Parent commit : yostqsxw b7bc1dda e f | (no description set) + "###); } #[test]