// Copyright 2025 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 itertools::Itertools as _; use jj_lib::object_id::ObjectId as _; use jj_lib::op_store::OperationId; use crate::cli_util::CommandHelper; use crate::command_error::CommandError; use crate::command_error::internal_error; use crate::command_error::user_error; use crate::commands::operation::DEFAULT_REVERT_WHAT; use crate::commands::operation::view_with_desired_portions_restored; use crate::commands::undo::UNDO_OP_DESC_PREFIX; use crate::ui::Ui; /// Redo the most recently undone operation /// /// This is the natural counterpart of `jj undo`. Repeated invocations of `jj /// undo` and `jj redo` act similarly to Undo/Redo commands in a text editor. /// /// Use `jj op log` to visualize the log of past operations, including a /// detailed description of any past undo/redo operations. See also `jj op /// restore` to explicitly restore an older operation by its id (available in /// the operation log). #[derive(clap::Args, Clone, Debug)] pub struct RedoArgs {} const REDO_OP_DESC_PREFIX: &str = "redo: restore to operation "; pub async fn cmd_redo( ui: &mut Ui, command: &CommandHelper, _: &RedoArgs, ) -> Result<(), CommandError> { let mut workspace_command = command.workspace_helper(ui).await?; let mut target_op = workspace_command.repo().operation().clone(); // Growing the "redo-stack" works very similar to the // [undo-stack](./undo.rs). `jj redo` and `jj undo` track their stacks // separately. // // - If the operation to redo is a regular one (neither an undo- or // redo-operation): Fail, because there is nothing to redo. // - If the operation to redo is an undo-operation, try to redo it (by restoring // its parent operation). // - If the operation to redo is a redo-operation itself, redo the operation the // early redo-operation restored to. // - If the operation to restore to is a redo-operation itself, restore directly // to the original operation. This avoids creating a linked list of // redo-operations, which subsequently may have to be walked with an // inefficient loop. // // This described behavior leads to "jumping over" old redo-stacks if the // current one grows into it. Consider the following op-log example, where // redo-stacks are shown on the left and undo-stacks on the right: // // +------- "redo: restore C" * I // | | // | +----- "redo: restore D" * H // | | | // | | * G "undo: restore A" -------+ // | | | | // | | +- "redo: restore D" * F | // | | | | | // | | | * E "undo: restore A" ---+ | // | | | | | | // | +-> +----------------> * D "undo: restore B" -+ | | // | | | | | // +----------------------> * C | | | // | | | | // * B <----------------+ | | // | | | // * A <------------------+ <-+ // // The first interesting operation here is H: // - Attempt to redo G. // - G is an undo-operation, so attempt to restore its parent F. // - F is a redo-operation. Restore its original operation D, instead of F. // // The operation I is also noteworthy: // - Attempt to redo H. // - H is a redo-operation restoring to D, so attempt to redo D. // - D is an undo-operation. Redo it by restoring its parent C. // if let Some(target_op_hex) = target_op .metadata() .description .strip_prefix(REDO_OP_DESC_PREFIX) { let target_op_id = OperationId::try_from_hex(target_op_hex).ok_or_else(|| { internal_error("Failed to parse ID of target operation in redo-stack") })?; target_op = workspace_command .repo() .loader() .load_operation(&target_op_id) .await?; } if !target_op .metadata() .description .starts_with(UNDO_OP_DESC_PREFIX) { // cannot redo a non-undo-operation return Err(user_error("Nothing to redo")); } let mut target_op_parent = target_op .parents() .await? .into_iter() .exactly_one() .map_err(|_| internal_error("Undo operation should have a single parent"))?; // Avoid the creation of a linked list by restoring to the original // operation directly, if we're about to restore a redo-operation. If // we didn't do this, repeated calls of `jj undo; jj redo` would create // an ever-growing linked list of redo-operations that restore each // other. Calling `jj redo` one more time would have to redo a potential // undo-operation at the very beginning of the linked list, which would // require walking the entire thing unnecessarily. if let Some(target_op_parent_hex) = target_op_parent .metadata() .description .strip_prefix(REDO_OP_DESC_PREFIX) { let target_op_parent_id = OperationId::try_from_hex(target_op_parent_hex).ok_or_else(|| { internal_error("Failed to parse ID of target operation's parent in redo-stack") })?; target_op_parent = workspace_command .repo() .loader() .load_operation(&target_op_parent_id) .await?; } let mut tx = workspace_command.start_transaction(); let new_view = view_with_desired_portions_restored( target_op_parent.view().await?.store_view(), tx.base_repo().view().store_view(), &DEFAULT_REVERT_WHAT, ); tx.repo_mut().set_view(new_view); if let Some(mut formatter) = ui.status_formatter() { write!(formatter, "Restored to operation: ")?; let template = tx.base_workspace_helper().operation_summary_template(); template.format(&target_op_parent, formatter.as_mut())?; writeln!(formatter)?; } tx.finish( ui, format!("{REDO_OP_DESC_PREFIX}{}", target_op_parent.id().hex()), ) .await?; Ok(()) }