commands: move unsquash code to unsquash.rs

This commit is contained in:
Antoine Cezar 2023-11-02 20:50:21 +01:00 committed by Antoine Cezar
parent 8bb0383ad5
commit 9d72fa2a51
2 changed files with 125 additions and 102 deletions

View file

@ -49,6 +49,7 @@ mod sparse;
mod split;
mod squash;
mod status;
mod unsquash;
mod util;
use std::fmt::Debug;
@ -143,7 +144,7 @@ enum Commands {
Util(util::UtilCommands),
/// Undo an operation (shortcut for `jj op undo`)
Undo(operation::OperationUndoArgs),
Unsquash(UnsquashArgs),
Unsquash(unsquash::UnsquashArgs),
Untrack(UntrackArgs),
Version(VersionArgs),
#[command(subcommand)]
@ -162,28 +163,6 @@ struct UntrackArgs {
paths: Vec<String>,
}
/// Move changes from a revision's parent into the revision
///
/// After moving the changes out of the parent, the child revision will have the
/// same content state as before. If moving the change out of the parent change
/// made it empty compared to its parent, it will be abandoned. Without
/// `--interactive`, the parent change will always become empty.
///
/// If the source became empty 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.
#[derive(clap::Args, Clone, Debug)]
#[command(visible_alias = "unamend")]
struct UnsquashArgs {
#[arg(long, short, default_value = "@")]
revision: RevisionArg,
/// Interactively choose which parts to unsquash
// TODO: It doesn't make much sense to run this without -i. We should make that
// the default.
#[arg(long, short)]
interactive: bool,
}
/// Commands for working with workspaces
///
/// Workspaces let you add additional working copies attached to the same repo.
@ -438,84 +417,6 @@ fn combine_messages(
Ok(description)
}
#[instrument(skip_all)]
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(&format!("unsquash commit {}", commit.id().hex()));
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();
}
// Abandon the parent if it is now empty (always the case in the non-interactive
// case).
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)?;
// Commit the new child on top of the parent's parents.
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()?;
// Commit the new child on top of the new parent.
tx.mut_repo()
.rewrite_commit(command.settings(), &commit)
.set_parents(vec![new_parent.id().clone()])
.write()?;
}
tx.finish(ui)?;
Ok(())
}
fn description_template_for_commit(
ui: &Ui,
settings: &UserSettings,
@ -833,7 +734,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
Commands::New(sub_args) => new::cmd_new(ui, command_helper, sub_args),
Commands::Move(sub_args) => r#move::cmd_move(ui, command_helper, sub_args),
Commands::Squash(sub_args) => squash::cmd_squash(ui, command_helper, sub_args),
Commands::Unsquash(sub_args) => cmd_unsquash(ui, command_helper, sub_args),
Commands::Unsquash(sub_args) => unsquash::cmd_unsquash(ui, command_helper, sub_args),
Commands::Restore(sub_args) => restore::cmd_restore(ui, command_helper, sub_args),
Commands::Run(sub_args) => run::cmd_run(ui, command_helper, sub_args),
Commands::Diffedit(sub_args) => diffedit::cmd_diffedit(ui, command_helper, sub_args),

View file

@ -0,0 +1,122 @@
// Copyright 2020 The Jujutsu Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
use jj_lib::backend::ObjectId;
use jj_lib::matchers::EverythingMatcher;
use jj_lib::rewrite::merge_commit_trees;
use tracing::instrument;
use super::combine_messages;
use crate::cli_util::{user_error, CommandError, CommandHelper, RevisionArg};
use crate::ui::Ui;
/// Move changes from a revision's parent into the revision
///
/// After moving the changes out of the parent, the child revision will have the
/// same content state as before. If moving the change out of the parent change
/// made it empty compared to its parent, it will be abandoned. Without
/// `--interactive`, the parent change will always become empty.
///
/// If the source became empty 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.
#[derive(clap::Args, Clone, Debug)]
#[command(visible_alias = "unamend")]
pub(crate) struct UnsquashArgs {
#[arg(long, short, default_value = "@")]
revision: RevisionArg,
/// Interactively choose which parts to unsquash
// TODO: It doesn't make much sense to run this without -i. We should make that
// the default.
#[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(&format!("unsquash commit {}", commit.id().hex()));
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();
}
// Abandon the parent if it is now empty (always the case in the non-interactive
// case).
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)?;
// Commit the new child on top of the parent's parents.
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()?;
// Commit the new child on top of the new parent.
tx.mut_repo()
.rewrite_commit(command.settings(), &commit)
.set_parents(vec![new_parent.id().clone()])
.write()?;
}
tx.finish(ui)?;
Ok(())
}