From 0c832e71bf994c70a3609cb098f5e563a3d0fc66 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 | 135 +++++++++++++++++++++++++++++++ tests/test_next_prev_commands.rs | 1 + 2 files changed, 136 insertions(+) create mode 100644 tests/test_next_prev_commands.rs diff --git a/src/commands/mod.rs b/src/commands/mod.rs index 19a5915ee2..a55263e51c 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. + #[default = "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. + #[default = "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,75 @@ 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}"))?; + if edit { + tx.edit(&parent).unwrap(); + tx.finish(ui)?; + 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)?; + 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}"); + + // 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 +3536,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..33794a6ed1 --- /dev/null +++ b/tests/test_next_prev_commands.rs @@ -0,0 +1 @@ +// TODO: Write insta tests for `jj next` and `jj prev`