use jj_lib::matchers::EverythingMatcher;
use jj_lib::object_id::ObjectId;
use jj_lib::rewrite::merge_commit_trees;
use tracing::instrument;
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
use crate::description_util::combine_messages;
use crate::ui::Ui;
#[derive(clap::Args, Clone, Debug)]
#[command(visible_alias = "unamend")]
pub(crate) struct UnsquashArgs {
#[arg(long, short, default_value = "@")]
revision: RevisionArg,
#[arg(long, short)]
interactive: bool,
}
#[instrument(skip_all)]
pub(crate) fn cmd_unsquash(
ui: &mut Ui,
command: &CommandHelper,
args: &UnsquashArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let commit = workspace_command.resolve_single_rev(&args.revision, ui)?;
workspace_command.check_rewritable([&commit])?;
let parents = commit.parents();
if parents.len() != 1 {
return Err(user_error("Cannot unsquash merge commits"));
}
let parent = &parents[0];
workspace_command.check_rewritable(&parents[..1])?;
let mut tx = workspace_command.start_transaction();
let parent_base_tree = merge_commit_trees(tx.repo(), &parent.parents())?;
let new_parent_tree_id;
if args.interactive {
let instructions = format!(
"\
You are moving changes from: {}
into its child: {}
The diff initially shows the parent commit's changes.
Adjust the right side until it shows the contents you want to keep in
the parent commit. The changes you edited out will be moved into the
child commit. If you don't make any changes, then the operation will be
aborted.
",
tx.format_commit_summary(parent),
tx.format_commit_summary(&commit)
);
let parent_tree = parent.tree()?;
new_parent_tree_id = tx.edit_diff(
ui,
&parent_base_tree,
&parent_tree,
&EverythingMatcher,
&instructions,
)?;
if new_parent_tree_id == parent_base_tree.id() {
return Err(user_error("No changes selected"));
}
} else {
new_parent_tree_id = parent_base_tree.id().clone();
}
if new_parent_tree_id == parent_base_tree.id() {
tx.mut_repo().record_abandoned_commit(parent.id().clone());
let description =
combine_messages(tx.base_repo(), parent, &commit, command.settings(), true)?;
tx.mut_repo()
.rewrite_commit(command.settings(), &commit)
.set_parents(parent.parent_ids().to_vec())
.set_description(description)
.write()?;
} else {
let new_parent = tx
.mut_repo()
.rewrite_commit(command.settings(), parent)
.set_tree_id(new_parent_tree_id)
.set_predecessors(vec![parent.id().clone(), commit.id().clone()])
.write()?;
tx.mut_repo()
.rewrite_commit(command.settings(), &commit)
.set_parents(vec![new_parent.id().clone()])
.write()?;
}
tx.finish(ui, format!("unsquash commit {}", commit.id().hex()))?;
Ok(())
}