Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: in the commit message you still talk about movediff/diffmove when the actual command name now is amend. I still am unconvinced on it though.

Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
tags. These keywords may be useful in non-colocated Git repositories where
local and exported `@git` tags can point to different revisions.

* `jj amend` moves diffs between revisions, (similar to squash)

### Fixed bugs

* `jj metaedit --author-timestamp` twice with the same value no longer
Expand Down
12 changes: 11 additions & 1 deletion cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ enum Command {
Describe(describe::DescribeArgs),
Diff(diff::DiffArgs),
Diffedit(diffedit::DiffeditArgs),
Amend(squash::AmendArgs),
Duplicate(duplicate::DuplicateArgs),
Edit(edit::EditArgs),
#[command(alias = "obslog", visible_alias = "evolution-log")]
Expand Down Expand Up @@ -180,6 +181,11 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
Command::Describe(args) => describe::cmd_describe(ui, command_helper, args),
Command::Diff(args) => diff::cmd_diff(ui, command_helper, args),
Command::Diffedit(args) => diffedit::cmd_diffedit(ui, command_helper, args),
Command::Amend(args) => squash::cmd_squash_or_amend(
ui,
command_helper,
squash::SquashOrAmendArgs::AmendArgs(args),
),
Command::Duplicate(args) => duplicate::cmd_duplicate(ui, command_helper, args),
Command::Edit(args) => edit::cmd_edit(ui, command_helper, args),
Command::Evolog(args) => evolog::cmd_evolog(ui, command_helper, args),
Expand Down Expand Up @@ -212,7 +218,11 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
Command::Sign(args) => sign::cmd_sign(ui, command_helper, args),
Command::Sparse(args) => sparse::cmd_sparse(ui, command_helper, args),
Command::Split(args) => split::cmd_split(ui, command_helper, args),
Command::Squash(args) => squash::cmd_squash(ui, command_helper, args),
Command::Squash(args) => squash::cmd_squash_or_amend(
ui,
command_helper,
squash::SquashOrAmendArgs::SquashArgs(args),
),
Command::Status(args) => status::cmd_status(ui, command_helper, args),
Command::Tag(args) => tag::cmd_tag(ui, command_helper, args),
Command::Undo(args) => undo::cmd_undo(ui, command_helper, args),
Expand Down
188 changes: 132 additions & 56 deletions cli/src/commands/squash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,11 @@ use crate::description_util::join_message_paragraphs;
use crate::description_util::try_combine_messages;
use crate::ui::Ui;

/// Move changes from a revision into another revision
/// Combine revisions by moving changes from a revision into another revision.
///
/// Note: If you aren't trying to merge the revision metadata
/// (eg. description, bookmarks), or if you want to do a partial squash,
/// you probably want `jj amend`.
///
/// With the `-r` option, moves the changes from the specified revision to the
/// parent revision. Fails if there are several parent revisions (i.e., the
Expand All @@ -59,8 +63,8 @@ use crate::ui::Ui;
/// commit to the grandparent.
///
/// If, after moving changes out, the source revision is empty compared to its
/// parent(s), and `--keep-emptied` is not set, it will be abandoned. Without
/// `--interactive` or paths, the source revision will always be empty.
/// parent(s), it will be abandoned. Without `--interactive` or paths, the
/// source revision will always be empty.
///
/// If the source was abandoned and both the source and destination had a
/// non-empty description, you will be asked for the combined description. If
Expand All @@ -76,34 +80,17 @@ use crate::ui::Ui;
/// (if no `--from` is specified, `--from @` is assumed).
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct SquashArgs {
/// Revision to squash into its parent (default: @). Incompatible with the
/// experimental `-d`/`-A`/`-B` options.
#[arg(
long,
short,
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
revision: Option<RevisionArg>,
#[clap(flatten)]
pub(crate) common: CommonArgs,

/// Revision(s) to squash from (default: @)
#[arg(
long, short,
conflicts_with = "revision",
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
from: Vec<RevisionArg>,
/// The description to use for squashed revision (don't open editor)
#[arg(long = "message", short, value_name = "MESSAGE")]
pub(crate) message_paragraphs: Vec<String>,

/// Revision to squash into (default: @)
#[arg(
long, short = 't',
conflicts_with = "revision",
visible_alias = "to",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
into: Option<RevisionArg>,
/// Use the description of the destination revision and discard the
/// description(s) of the source revision(s)
#[arg(long, short, conflicts_with = "message_paragraphs")]
pub(crate) use_destination_message: bool,

/// (Experimental) The revision(s) to use as parent for the new commit (can
/// be repeated to create a merge commit)
Expand Down Expand Up @@ -145,14 +132,67 @@ pub(crate) struct SquashArgs {
)]
insert_before: Option<Vec<RevisionArg>>,

/// The description to use for squashed revision (don't open editor)
/// The source revision will not be abandoned
#[arg(long, short)]
keep_emptied: bool,
}

/// Move changes from a revision into another revision
///
/// With the `-r` option, moves the changes from the specified revision to the
/// parent revision. Fails if there are several parent revisions (i.e., the
/// given revision is a merge).
///
/// With the `--from` and/or `--into` options, moves changes from/to the given
/// revisions. If either is left out, it defaults to the working-copy commit.
/// For example, `jj squash --into @--` moves changes from the working-copy
/// commit to the grandparent.
///
/// EXPERIMENTAL FEATURES
///
/// An alternative squashing UI is available via the `-d`, `-A`, and `-B`
/// options. They can be used together with one or more `--from` options
/// (if no `--from` is specified, `--from @` is assumed).
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct AmendArgs {
#[clap(flatten)]
common: CommonArgs,

/// The description to overwrite for the destination revision.
#[arg(long = "message", short, value_name = "MESSAGE")]
message_paragraphs: Vec<String>,
pub(crate) message_paragraphs: Vec<String>,
}

/// Use the description of the destination revision and discard the
/// description(s) of the source revision(s)
#[arg(long, short, conflicts_with = "message_paragraphs")]
use_destination_message: bool,
#[derive(clap::Args, Clone, Debug)]
pub(crate) struct CommonArgs {
/// Revision to squash into its parent (default: @). Incompatible with the
/// experimental `-d`/`-A`/`-B` options.
#[arg(
long,
short,
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
revision: Option<RevisionArg>,

/// Revision(s) to squash from (default: @)
#[arg(
long, short,
conflicts_with = "revision",
value_name = "REVSETS",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
from: Vec<RevisionArg>,

/// Revision to squash into (default: @)
#[arg(
long, short = 't',
conflicts_with = "revision",
visible_alias = "to",
value_name = "REVSET",
add = ArgValueCompleter::new(complete::revset_expression_mutable),
)]
into: Option<RevisionArg>,

/// Interactively choose which parts to squash
#[arg(long, short)]
Expand All @@ -173,20 +213,59 @@ pub(crate) struct SquashArgs {
add = ArgValueCompleter::new(complete::squash_revision_files),
)]
paths: Vec<String>,
}

/// The source revision will not be abandoned
#[arg(long, short)]
keep_emptied: bool,
pub(crate) enum SquashOrAmendArgs<'a> {
SquashArgs(&'a SquashArgs),
AmendArgs(&'a AmendArgs),
}

#[instrument(skip_all)]
pub(crate) fn cmd_squash(
pub(crate) fn cmd_squash_or_amend(
ui: &mut Ui,
command: &CommandHelper,
args: &SquashArgs,
args: SquashOrAmendArgs,
) -> Result<(), CommandError> {
let keep_emptied = match args {
SquashOrAmendArgs::SquashArgs(args) => {
if args.keep_emptied {
writeln!(
ui.warning_default(),
"`jj squash --keep-emptied` has been deprecated and replaced with `jj amend`",
)?;
}
args.keep_emptied
}
SquashOrAmendArgs::AmendArgs(_) => true,
};
let message_paragraphs = match args {
SquashOrAmendArgs::SquashArgs(args) => &args.message_paragraphs,
SquashOrAmendArgs::AmendArgs(args) => &args.message_paragraphs,
};
let use_destination_message = match args {
SquashOrAmendArgs::SquashArgs(args) => args.use_destination_message,
SquashOrAmendArgs::AmendArgs(_) => Default::default(),
};
let insert_before = match args {
SquashOrAmendArgs::SquashArgs(args) => &args.insert_before,
SquashOrAmendArgs::AmendArgs(_) => &None,
};
let insert_after = match args {
SquashOrAmendArgs::SquashArgs(args) => &args.insert_after,
SquashOrAmendArgs::AmendArgs(_) => &None,
};
let destination = match args {
SquashOrAmendArgs::SquashArgs(args) => &args.destination,
SquashOrAmendArgs::AmendArgs(_) => &None,
};

let args = match args {
SquashOrAmendArgs::SquashArgs(args) => &args.common,
SquashOrAmendArgs::AmendArgs(args) => &args.common,
};

let insert_destination_commit =
args.destination.is_some() || args.insert_after.is_some() || args.insert_before.is_some();
destination.is_some() || insert_after.is_some() || insert_before.is_some();

let mut workspace_command = command.workspace_helper(ui)?;

Expand Down Expand Up @@ -253,9 +332,9 @@ pub(crate) fn cmd_squash(
let (parent_ids, child_ids) = compute_commit_location(
ui,
tx.base_workspace_helper(),
args.destination.as_deref(),
args.insert_after.as_deref(),
args.insert_before.as_deref(),
destination.as_deref(),
insert_after.as_deref(),
insert_before.as_deref(),
"squashed commit",
)?;
let parent_commits: Vec<_> = parent_ids
Expand Down Expand Up @@ -310,15 +389,12 @@ pub(crate) fn cmd_squash(
tx.base_workspace_helper()
.diff_selector(ui, args.tool.as_deref(), args.interactive)?;
let text_editor = tx.base_workspace_helper().text_editor()?;
let description = SquashedDescription::from_args(args);
let description = SquashedDescription::from_args(message_paragraphs, use_destination_message);

let source_commits = select_diff(&tx, &sources, &destination, &matcher, &diff_selector)?;
if let Some(squashed) = rewrite::squash_commits(
tx.repo_mut(),
&source_commits,
&destination,
args.keep_emptied,
)? {
if let Some(squashed) =
rewrite::squash_commits(tx.repo_mut(), &source_commits, &destination, keep_emptied)?
{
let mut commit_builder = squashed.commit_builder.detach();
let new_description = match description {
SquashedDescription::Exact(description) => {
Expand Down Expand Up @@ -434,14 +510,14 @@ enum SquashedDescription {
}

impl SquashedDescription {
fn from_args(args: &SquashArgs) -> Self {
fn from_args(message_paragraphs: &[String], use_destination_message: bool) -> Self {
// These options are incompatible and Clap is configured to prevent this.
assert!(args.message_paragraphs.is_empty() || !args.use_destination_message);
assert!(message_paragraphs.is_empty() || !use_destination_message);

if !args.message_paragraphs.is_empty() {
let desc = join_message_paragraphs(&args.message_paragraphs);
if !message_paragraphs.is_empty() {
let desc = join_message_paragraphs(message_paragraphs);
Self::Exact(desc)
} else if args.use_destination_message {
} else if use_destination_message {
Self::UseDestination
} else {
Self::Combine
Expand Down
47 changes: 40 additions & 7 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This document contains the help content for the `jj` command-line program.
* [`jj describe`↴](#jj-describe)
* [`jj diff`↴](#jj-diff)
* [`jj diffedit`↴](#jj-diffedit)
* [`jj amend`↴](#jj-amend)
* [`jj duplicate`↴](#jj-duplicate)
* [`jj edit`↴](#jj-edit)
* [`jj evolog`↴](#jj-evolog)
Expand Down Expand Up @@ -141,6 +142,7 @@ To get started, see the tutorial [`jj help -k tutorial`].
* `describe` — Update the change description or other metadata [default alias: desc]
* `diff` — Compare file contents between two revisions
* `diffedit` — Touch up the content changes in a revision with a diff editor
* `amend` — Move changes from a revision into another revision
* `duplicate` — Create new changes with the same content as existing ones
* `edit` — Sets the specified revision as the working-copy revision
* `evolog` — Show how a change has evolved over time
Expand Down Expand Up @@ -168,7 +170,7 @@ To get started, see the tutorial [`jj help -k tutorial`].
* `simplify-parents` — Simplify parent edges for the specified revision(s)
* `sparse` — Manage which paths from the working-copy commit are present in the working copy
* `split` — Split a revision in two
* `squash` — Move changes from a revision into another revision
* `squash` — Combine revisions by moving changes from a revision into another revision
* `status` — Show high-level repo status [default alias: st]
* `tag` — Manage tags
* `undo` — Undo the last operation
Expand Down Expand Up @@ -909,6 +911,35 @@ See `jj restore` if you want to move entire files from one revision to another.



## `jj amend`

Move changes from a revision into another revision

With the `-r` option, moves the changes from the specified revision to the parent revision. Fails if there are several parent revisions (i.e., the given revision is a merge).

With the `--from` and/or `--into` options, moves changes from/to the given revisions. If either is left out, it defaults to the working-copy commit. For example, `jj squash --into @--` moves changes from the working-copy commit to the grandparent.

EXPERIMENTAL FEATURES

An alternative squashing UI is available via the `-d`, `-A`, and `-B` options. They can be used together with one or more `--from` options (if no `--from` is specified, `--from @` is assumed).

**Usage:** `jj amend [OPTIONS] [FILESETS]...`

###### **Arguments:**

* `<FILESETS>` — Move only changes to these paths (instead of all paths)

###### **Options:**

* `-r`, `--revision <REVSET>` — Revision to squash into its parent (default: @). Incompatible with the experimental `-d`/`-A`/`-B` options
* `-f`, `--from <REVSETS>` — Revision(s) to squash from (default: @)
* `-t`, `--into <REVSET>` [alias: `to`] — Revision to squash into (default: @)
* `-i`, `--interactive` — Interactively choose which parts to squash
* `--tool <NAME>` — Specify diff editor to be used (implies --interactive)
* `-m`, `--message <MESSAGE>` — The description to overwrite for the destination revision



## `jj duplicate`

Create new changes with the same content as existing ones
Expand Down Expand Up @@ -2715,13 +2746,15 @@ Splitting an empty commit is not supported because the same effect can be achiev

## `jj squash`

Move changes from a revision into another revision
Combine revisions by moving changes from a revision into another revision.

Note: If you aren't trying to merge the revision metadata (eg. description, bookmarks), or if you want to do a partial squash, you probably want `jj amend`.

With the `-r` option, moves the changes from the specified revision to the parent revision. Fails if there are several parent revisions (i.e., the given revision is a merge).

With the `--from` and/or `--into` options, moves changes from/to the given revisions. If either is left out, it defaults to the working-copy commit. For example, `jj squash --into @--` moves changes from the working-copy commit to the grandparent.

If, after moving changes out, the source revision is empty compared to its parent(s), and `--keep-emptied` is not set, it will be abandoned. Without `--interactive` or paths, the source revision will always be empty.
If, after moving changes out, the source revision is empty compared to its parent(s), it will be abandoned. Without `--interactive` or paths, the source revision will always be empty.

If the source was abandoned and both the source and destination had a non-empty description, you will be asked for the combined description. If either was empty, then the other one will be used.

Expand All @@ -2742,13 +2775,13 @@ An alternative squashing UI is available via the `-d`, `-A`, and `-B` options. T
* `-r`, `--revision <REVSET>` — Revision to squash into its parent (default: @). Incompatible with the experimental `-d`/`-A`/`-B` options
* `-f`, `--from <REVSETS>` — Revision(s) to squash from (default: @)
* `-t`, `--into <REVSET>` [alias: `to`] — Revision to squash into (default: @)
* `-i`, `--interactive` — Interactively choose which parts to squash
* `--tool <NAME>` — Specify diff editor to be used (implies --interactive)
* `-m`, `--message <MESSAGE>` — The description to use for squashed revision (don't open editor)
* `-u`, `--use-destination-message` — Use the description of the destination revision and discard the description(s) of the source revision(s)
* `-d`, `--destination <REVSETS>` — (Experimental) The revision(s) to use as parent for the new commit (can be repeated to create a merge commit)
* `-A`, `--insert-after <REVSETS>` [alias: `after`] — (Experimental) The revision(s) to insert the new commit after (can be repeated to create a merge commit)
* `-B`, `--insert-before <REVSETS>` [alias: `before`] — (Experimental) The revision(s) to insert the new commit before (can be repeated to create a merge commit)
* `-m`, `--message <MESSAGE>` — The description to use for squashed revision (don't open editor)
* `-u`, `--use-destination-message` — Use the description of the destination revision and discard the description(s) of the source revision(s)
* `-i`, `--interactive` — Interactively choose which parts to squash
* `--tool <NAME>` — Specify diff editor to be used (implies --interactive)
* `-k`, `--keep-emptied` — The source revision will not be abandoned


Expand Down
Loading