mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-31 23:25:09 +00:00
I'm going to add a prefix resolution method to OpStore, but OpStore is unrelated to the index. I think ObjectId, HexPrefix, and PrefixResolution can be extracted to this module.
122 lines
4.6 KiB
Rust
122 lines
4.6 KiB
Rust
// 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::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;
|
|
|
|
/// 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();
|
|
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, format!("unsquash commit {}", commit.id().hex()))?;
|
|
Ok(())
|
|
}
|