136 lines · 5.0 KB · Rust
RawDownload
// Copyright 2020-2023 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_complete::ArgValueCandidates;use itertools::Itertools as _;use jj_lib::object_id::ObjectId as _;use jj_lib::op_store::OpStoreError;use jj_lib::operation::Operation;use jj_lib::repo::Repo as _; use super::view_with_desired_portions_restored;use super::UndoWhatToRestore;use super::DEFAULT_UNDO_WHAT;use crate::cli_util::CommandHelper;use crate::command_error::user_error;use crate::command_error::CommandError;use crate::complete;use crate::ui::Ui; /// Create a new operation that undoes an earlier operation////// This undoes an individual operation by applying the inverse of the/// operation.#[derive(clap::Args, Clone, Debug)]pub struct OperationUndoArgs {    /// The operation to undo    ///    /// Use `jj op log` to find an operation to undo.    #[arg(default_value = "@", add = ArgValueCandidates::new(complete::operations))]    operation: String,     /// What portions of the local state to restore (can be repeated)    ///    /// This option is EXPERIMENTAL.    #[arg(long, value_enum, default_values_t = DEFAULT_UNDO_WHAT)]    what: Vec<UndoWhatToRestore>,} // Checks whether `op` resets the view of `parent_op` to the view of the// grandparent op.//// This is a necessary condition for `op` to be an undo of `parent_op` but is// not sufficient. For example, deleting a bookmark also resets the view// similarly but is not a literal `undo` operation.fn resets_view_of(op: &Operation, parent_op: &Operation) -> Result<bool, OpStoreError> {    let Ok(grandparent_op) = parent_op.parents().exactly_one() else {        return Ok(false);    };    Ok(op.view_id() == grandparent_op?.view_id())} fn tx_description(op: &Operation) -> String {    format!("undo operation {}", op.id().hex())} pub fn cmd_op_undo(    ui: &mut Ui,    command: &CommandHelper,    args: &OperationUndoArgs,) -> Result<(), CommandError> {    let mut workspace_command = command.workspace_helper(ui)?;    let bad_op = workspace_command.resolve_single_op(&args.operation)?;    let parent_of_bad_op = match bad_op.parents().at_most_one() {        Ok(Some(parent_of_bad_op)) => parent_of_bad_op?,        Ok(None) => return Err(user_error("Cannot undo root operation")),        Err(_) => return Err(user_error("Cannot undo a merge operation")),    };     let mut tx = workspace_command.start_transaction();    let repo_loader = tx.base_repo().loader();    let bad_repo = repo_loader.load_at(&bad_op)?;    let parent_repo = repo_loader.load_at(&parent_of_bad_op)?;    tx.repo_mut().merge(&bad_repo, &parent_repo)?;    let new_view = view_with_desired_portions_restored(        tx.repo().view().store_view(),        tx.base_repo().view().store_view(),        &args.what,    );    tx.repo_mut().set_view(new_view);    if let Some(mut formatter) = ui.status_formatter() {        write!(formatter, "Undid operation: ")?;        let template = tx.base_workspace_helper().operation_summary_template();        template.format(&bad_op, formatter.as_mut())?;        writeln!(formatter)?;    }    tx.finish(ui, tx_description(&bad_op))?;     // Check if the user performed a "double undo", i.e. the current `undo` (C)    // reverts an immediately preceding `undo` (B) that is itself an `undo` of the    // operation preceding it (A).    //    //    C (undo of B)    // @  B (`bad_op` = undo of A)    // ○  A    //    // An exception is made for when the user specified the immediately preceding    // `undo` with an op set. In this situation, the user's intent is clear, so    // a warning is not shown.    //    // Note that undoing an older `undo` does not constitute a "double undo". For    // example, the current `undo` (D) here reverts an `undo` B that is not the    // immediately preceding operation (C). A warning is not shown in this case.    //    //    D (undo of B)    // @  C (unrelated operation)    // ○  B (`bad_op` = undo of A)    // ○  A    if args.operation == "@"        && resets_view_of(&bad_op, &parent_of_bad_op)?        && bad_op.metadata().description == tx_description(&parent_of_bad_op)    {        writeln!(            ui.warning_default(),            "The second-last `jj undo` was reverted by the latest `jj undo`. The repo is now in \             the same state as it was before the second-last `jj undo`."        )?;        writeln!(            ui.hint_default(),            "To undo multiple operations, use `jj op log` to see past states and `jj op restore` \             to restore one of these states."        )?;    }     Ok(())}