Skip to content

Commit

Permalink
commands: move move code to move.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
AntoineCezar committed Oct 29, 2023
1 parent 6af13ea commit 92739eb
Show file tree
Hide file tree
Showing 2 changed files with 145 additions and 117 deletions.
120 changes: 3 additions & 117 deletions cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ mod git;
mod init;
mod interdiff;
mod log;
mod r#move;
mod new;
mod operation;

Expand Down Expand Up @@ -116,7 +117,7 @@ enum Commands {
/// This is the same as `jj new`, except that it requires at least two
/// arguments.
Merge(new::NewArgs),
Move(MoveArgs),
Move(r#move::MoveArgs),
New(new::NewArgs),
Next(NextArgs),
Obslog(ObslogArgs),
Expand Down Expand Up @@ -289,35 +290,6 @@ struct PrevArgs {
edit: bool,
}

/// Move changes from one revision into another
///
/// Use `--interactive` to move only part of the source revision into the
/// destination. The selected changes (or all the changes in the source revision
/// if not using `--interactive`) will be moved into the destination. The
/// changes will be removed from the source. If that means that the source is
/// now empty compared to its parent, it will be abandoned. Without
/// `--interactive`, the source change will always be 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(group(ArgGroup::new("to_move").args(&["from", "to"]).multiple(true).required(true)))]
struct MoveArgs {
/// Move part of this change into the destination
#[arg(long)]
from: Option<RevisionArg>,
/// Move part of the source into this change
#[arg(long)]
to: Option<RevisionArg>,
/// Interactively choose which parts to move
#[arg(long, short)]
interactive: bool,
/// Move only changes to these paths (instead of all paths)
#[arg(conflicts_with = "interactive", value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>,
}

/// Move changes from a revision into its parent
///
/// After moving the changes into the parent, the child revision will have the
Expand Down Expand Up @@ -1368,92 +1340,6 @@ fn combine_messages(
Ok(description)
}

#[instrument(skip_all)]
fn cmd_move(ui: &mut Ui, command: &CommandHelper, args: &MoveArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let source = workspace_command.resolve_single_rev(args.from.as_deref().unwrap_or("@"), ui)?;
let mut destination =
workspace_command.resolve_single_rev(args.to.as_deref().unwrap_or("@"), ui)?;
if source.id() == destination.id() {
return Err(user_error("Source and destination cannot be the same."));
}
workspace_command.check_rewritable([&source, &destination])?;
let matcher = workspace_command.matcher_from_values(&args.paths)?;
let mut tx = workspace_command.start_transaction(&format!(
"move changes from {} to {}",
source.id().hex(),
destination.id().hex()
));
let parent_tree = merge_commit_trees(tx.repo(), &source.parents())?;
let source_tree = source.tree()?;
let instructions = format!(
"\
You are moving changes from: {}
into commit: {}
The left side of the diff shows the contents of the parent commit. The
right side initially shows the contents of the commit you're moving
changes from.
Adjust the right side until the diff shows the changes you want to move
to the destination. If you don't make any changes, then all the changes
from the source will be moved into the destination.
",
tx.format_commit_summary(&source),
tx.format_commit_summary(&destination)
);
let new_parent_tree_id = tx.select_diff(
ui,
&parent_tree,
&source_tree,
matcher.as_ref(),
&instructions,
args.interactive,
)?;
if args.interactive && new_parent_tree_id == parent_tree.id() {
return Err(user_error("No changes to move"));
}
let new_parent_tree = tx.repo().store().get_root_tree(&new_parent_tree_id)?;
// Apply the reverse of the selected changes onto the source
let new_source_tree = source_tree.merge(&new_parent_tree, &parent_tree)?;
let abandon_source = new_source_tree.id() == parent_tree.id();
if abandon_source {
tx.mut_repo().record_abandoned_commit(source.id().clone());
} else {
tx.mut_repo()
.rewrite_commit(command.settings(), &source)
.set_tree_id(new_source_tree.id().clone())
.write()?;
}
if tx.repo().index().is_ancestor(source.id(), destination.id()) {
// If we're moving changes to a descendant, first rebase descendants onto the
// rewritten source. Otherwise it will likely already have the content
// changes we're moving, so applying them will have no effect and the
// changes will disappear.
let mut rebaser = tx.mut_repo().create_descendant_rebaser(command.settings());
rebaser.rebase_all()?;
let rebased_destination_id = rebaser.rebased().get(destination.id()).unwrap().clone();
destination = tx.mut_repo().store().get_commit(&rebased_destination_id)?;
}
// Apply the selected changes onto the destination
let destination_tree = destination.tree()?;
let new_destination_tree = destination_tree.merge(&parent_tree, &new_parent_tree)?;
let description = combine_messages(
tx.base_repo(),
&source,
&destination,
command.settings(),
abandon_source,
)?;
tx.mut_repo()
.rewrite_commit(command.settings(), &destination)
.set_tree_id(new_destination_tree.id().clone())
.set_description(description)
.write()?;
tx.finish(ui)?;
Ok(())
}

#[instrument(skip_all)]
fn cmd_squash(ui: &mut Ui, command: &CommandHelper, args: &SquashArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
Expand Down Expand Up @@ -2624,7 +2510,7 @@ pub fn run_command(ui: &mut Ui, command_helper: &CommandHelper) -> Result<(), Co
Commands::Next(sub_args) => cmd_next(ui, command_helper, sub_args),
Commands::Prev(sub_args) => cmd_prev(ui, command_helper, sub_args),
Commands::New(sub_args) => new::cmd_new(ui, command_helper, sub_args),
Commands::Move(sub_args) => cmd_move(ui, command_helper, sub_args),
Commands::Move(sub_args) => r#move::cmd_move(ui, command_helper, sub_args),
Commands::Squash(sub_args) => cmd_squash(ui, command_helper, sub_args),
Commands::Unsquash(sub_args) => cmd_unsquash(ui, command_helper, sub_args),
Commands::Restore(sub_args) => cmd_restore(ui, command_helper, sub_args),
Expand Down
142 changes: 142 additions & 0 deletions cli/src/commands/move.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
// 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 clap::ArgGroup;
use jj_lib::backend::ObjectId;
use jj_lib::repo::Repo;
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 one revision into another
///
/// Use `--interactive` to move only part of the source revision into the
/// destination. The selected changes (or all the changes in the source revision
/// if not using `--interactive`) will be moved into the destination. The
/// changes will be removed from the source. If that means that the source is
/// now empty compared to its parent, it will be abandoned. Without
/// `--interactive`, the source change will always be 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(group(ArgGroup::new("to_move").args(&["from", "to"]).multiple(true).required(true)))]
pub(crate) struct MoveArgs {
/// Move part of this change into the destination
#[arg(long)]
from: Option<RevisionArg>,
/// Move part of the source into this change
#[arg(long)]
to: Option<RevisionArg>,
/// Interactively choose which parts to move
#[arg(long, short)]
interactive: bool,
/// Move only changes to these paths (instead of all paths)
#[arg(conflicts_with = "interactive", value_hint = clap::ValueHint::AnyPath)]
paths: Vec<String>,
}

#[instrument(skip_all)]
pub(crate) fn cmd_move(
ui: &mut Ui,
command: &CommandHelper,
args: &MoveArgs,
) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let source = workspace_command.resolve_single_rev(args.from.as_deref().unwrap_or("@"), ui)?;
let mut destination =
workspace_command.resolve_single_rev(args.to.as_deref().unwrap_or("@"), ui)?;
if source.id() == destination.id() {
return Err(user_error("Source and destination cannot be the same."));
}
workspace_command.check_rewritable([&source, &destination])?;
let matcher = workspace_command.matcher_from_values(&args.paths)?;
let mut tx = workspace_command.start_transaction(&format!(
"move changes from {} to {}",
source.id().hex(),
destination.id().hex()
));
let parent_tree = merge_commit_trees(tx.repo(), &source.parents())?;
let source_tree = source.tree()?;
let instructions = format!(
"\
You are moving changes from: {}
into commit: {}
The left side of the diff shows the contents of the parent commit. The
right side initially shows the contents of the commit you're moving
changes from.
Adjust the right side until the diff shows the changes you want to move
to the destination. If you don't make any changes, then all the changes
from the source will be moved into the destination.
",
tx.format_commit_summary(&source),
tx.format_commit_summary(&destination)
);
let new_parent_tree_id = tx.select_diff(
ui,
&parent_tree,
&source_tree,
matcher.as_ref(),
&instructions,
args.interactive,
)?;
if args.interactive && new_parent_tree_id == parent_tree.id() {
return Err(user_error("No changes to move"));
}
let new_parent_tree = tx.repo().store().get_root_tree(&new_parent_tree_id)?;
// Apply the reverse of the selected changes onto the source
let new_source_tree = source_tree.merge(&new_parent_tree, &parent_tree)?;
let abandon_source = new_source_tree.id() == parent_tree.id();
if abandon_source {
tx.mut_repo().record_abandoned_commit(source.id().clone());
} else {
tx.mut_repo()
.rewrite_commit(command.settings(), &source)
.set_tree_id(new_source_tree.id().clone())
.write()?;
}
if tx.repo().index().is_ancestor(source.id(), destination.id()) {
// If we're moving changes to a descendant, first rebase descendants onto the
// rewritten source. Otherwise it will likely already have the content
// changes we're moving, so applying them will have no effect and the
// changes will disappear.
let mut rebaser = tx.mut_repo().create_descendant_rebaser(command.settings());
rebaser.rebase_all()?;
let rebased_destination_id = rebaser.rebased().get(destination.id()).unwrap().clone();
destination = tx.mut_repo().store().get_commit(&rebased_destination_id)?;
}
// Apply the selected changes onto the destination
let destination_tree = destination.tree()?;
let new_destination_tree = destination_tree.merge(&parent_tree, &new_parent_tree)?;
let description = combine_messages(
tx.base_repo(),
&source,
&destination,
command.settings(),
abandon_source,
)?;
tx.mut_repo()
.rewrite_commit(command.settings(), &destination)
.set_tree_id(new_destination_tree.id().clone())
.set_description(description)
.write()?;
tx.finish(ui)?;
Ok(())
}

0 comments on commit 92739eb

Please sign in to comment.