From f1a62b9c0bc31865676921d7d9246af56b7d3cd9 Mon Sep 17 00:00:00 2001 From: Philip Metzger Date: Sun, 22 Jan 2023 00:07:42 +0100 Subject: [PATCH] commands: Implement `next` and `prev` This is a naive implementation, which cannot deal with multiple children or parents stemming from merges. Note: I currently gave each command separate a separate argument struct for extensibility. Fixes #878 --- src/commands/mod.rs | 140 +++++++++++++++++++++++++++++++ tests/test_next_prev_commands.rs | 66 +++++++++++++++ 2 files changed, 206 insertions(+) create mode 100644 tests/test_next_prev_commands.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 19a5915ee2..b5adfae303 100644 --- a/src/commands/mod.rs +++ b/src/commands/mod.rs @@ -87,6 +87,8 @@ enum Commands { Diffedit(DiffeditArgs), Resolve(ResolveArgs), Split(SplitArgs), + Next(NextArgs), + Prev(PrevArgs), /// Merge work from multiple branches /// /// Unlike most other VCSs, `jj merge` does not implicitly include the @@ -468,6 +470,68 @@ struct NewArgs { insert_before: bool, } +/// Move the current working copy commit to the next child revision in the +/// repository. The command moves you to the next child in a linear fashion. +/// +/// F F @ +/// | | / +/// C @ => C +/// | / | +/// B B +/// +/// If `edit` is passed as an argument, it will move you directly to the child +/// revision. +/// +/// F F +/// | | +/// C C +/// | | +/// B @ => @ +/// | / | +/// A A +// TODO(#NNN): Handle multiple child revisions properly. +#[derive(clap::Args, Clone, Debug)] +struct NextArgs { + /// How many revisions to move forward. By default advances to the next + /// child. + #[arg(default_value = "1")] + amount: usize, + /// Instead of moving the empty commit from `jj new`, edit the child + /// revision directly. This mirrors the behavior of Mercurial and + /// Sapling. + edit: bool, +} + +/// Move the working copy commit to the parent of the current revision. +/// The command moves you to the parent in a linear fashion. +/// +/// F @ F +/// |/ | +/// A => A @ +/// | | / +/// B B +/// +/// If `edit` is passed as an argument, it will move the working copy commit +/// directly to the parent. +/// +/// F @ F +/// |/ | +/// C => C +/// | | +/// B @ +/// | | +/// A A +// TODO(#NNN): Handle multiple parents, e.g merges. +#[derive(clap::Args, Clone, Debug)] +struct PrevArgs { + /// How many revisions to move backward. By default moves to the parent. + #[arg(default_value = "1")] + amount: usize, + /// Edit the parent directly, instead of moving the empty revision. + /// This mirrors the behavior of Mercurial and Sapling. + edit: bool, +} + /// Move changes from one revision into another /// /// Use `--interactive` to move only part of the source revision into the @@ -2176,6 +2240,80 @@ fn combine_messages( Ok(description) } +fn cmd_next(ui: &mut Ui, command: &CommandHelper, args: &NextArgs) -> Result<(), CommandError> { + let mut workspace_command = command.workspace_helper(ui)?; + let child = resolve_destination_revs( + &workspace_command, + &["@-"], + /* allow_large_revsets= */ false, + ); + + let edit = args.edit; + assert!(args.amount == 1 || args.amount > 1); + // Handle the simple `jj next` call. + if args.amount == 1 {} + assert!(args.amount > 1, "Expected to descend to further children"); + + if edit {} + // TODO(#NNN) We currently cannot deal with multiple children, which resulted + // from branches. + Ok(()) +} + +fn cmd_prev(ui: &mut Ui, command: &CommandHelper, args: &NextArgs) -> Result<(), CommandError> { + let mut workspace_command = command.workspace_helper(ui)?; + let parent = workspace_command.resolve_single_rev("@-")?; + let edit = args.edit; + assert!(args.amount == 1 || args.amount > 1); + // Handle the simple case of a basic `prev` call. + if args.amount == 1 { + // TODO(#NNN): Handle multiple parents. + if parent.parents().len() > 1 { + return Err(user_error( + "Revision has multiple parents, see issue #NNN for more info", + )); + } + workspace_command.check_rewritable(&parent)?; + let mut tx = workspace_command.start_transaction(format!("prev: {current} -> {parent}")); + let root_commit = tx.base_repo().store().root_commit(); + // If we're editing, just move to the revision directly. + if edit { + if parent.id() == root_commit.id() { + return Err(user_error("Editing the root commit is not allowed.")); + } + tx.edit(&parent).unwrap(); + tx.finish(ui)?; + return Ok(()); + } + // Move the workspace commit after the parent. + let new_ws_revision = tx + .mut_repo() + .new_commit( + command.settings(), + target_id.clone(), + merged_tree.id().clone(), + ) + .write()?; + tx.edit(&new_ws_revision).unwrap(); + tx.finish(ui)?; + return Ok(()); + } + assert!(args.amount > 1, "Expected more parents to traverse"); + let amount = args.amount; + // Collect all parents and their parents. + let all_parents = cur_rev.parents().map(|parent| parent.id()).collect_vec(); + let mut tx = workspace_helper.start_transaction("prev: moved {amount} {current} -> {parent}"); + let root_commit = tx.base_repo().store().root_commit(); + // TODO(#NNN): We currently cannot deal with multiple parents. + if parent.parents().len() > 1 { + return Err(user_error( + "Revision has multiple parents,see issue #NNN for more info", + )); + } + if edit {} + Ok(()) +} + 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("@"))?; @@ -3403,6 +3541,8 @@ pub fn run_command( Commands::Duplicate(sub_args) => cmd_duplicate(ui, command_helper, sub_args), Commands::Abandon(sub_args) => cmd_abandon(ui, command_helper, sub_args), Commands::Edit(sub_args) => cmd_edit(ui, command_helper, sub_args), + 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) => cmd_new(ui, command_helper, sub_args), Commands::Move(sub_args) => cmd_move(ui, command_helper, sub_args), Commands::Squash(sub_args) => cmd_squash(ui, command_helper, sub_args), diff --git a/tests/test_next_prev_commands.rs b/tests/test_next_prev_commands.rs new file mode 100644 index 0000000000..2909e55f9b --- /dev/null +++ b/tests/test_next_prev_commands.rs @@ -0,0 +1,66 @@ +// Copyright 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. +// +// TODO: Finish tests for `prev` and `next` + +use crate::common::{get_stderr_string, TestEnvironment}; + +pub mod common; + +#[test] +fn test_next_simple() { + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_next_multiple_without_root() { + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_prev_simple() { + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_prev_multiple_without_root() { + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_next_fails_on_branches() { + // TODO(#NNN): Fix this behavior + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_prev_fails_on_multiple_parents() { + // TODO(#NNN): Fix this behavior + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_prev_onto_root() { + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_prev_editing() { + let test_env = TestEnvironment::default(); +} + +#[test] +fn test_next_editing() { + let test_env = TestEnvironment::default(); +}