Skip to content

Commit

Permalink
commands: Implement next and prev
Browse files Browse the repository at this point in the history
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
  • Loading branch information
PhilipMetzger committed Feb 16, 2023
1 parent e7bd7a6 commit 8bc732c
Show file tree
Hide file tree
Showing 2 changed files with 220 additions and 0 deletions.
154 changes: 154 additions & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -2099,6 +2163,94 @@ 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 children = resolve_destination_revs(
&workspace_command,
&["@+"],
/* allow_large_revsets= */ false,
)
.into_iter()
.collect_vec();

let edit = args.edit;
assert!(args.amount == 1 || args.amount > 1);
let amount = args.amount;
// Handle the simple `jj next` call.
if amount == 1 {}
assert!(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: &PrevArgs) -> Result<(), CommandError> {
let mut workspace_command = command.workspace_helper(ui)?;
let parents = resolve_destination_revs(
&workspace_command,
&["@-"],
/* allow_large_revsets= */ false,
)
.into_iter()
.collect_vec();
assert!(parents.len() >= 1, "expected a set of revisions for next");
let edit = args.edit;
assert!(args.amount == 1 || args.amount > 1);
// Handle the simple case of a basic `prev` call.
if args.amount == 1 {
let parent = parents[0];
// 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 current = workspace_command.repo().view().get_wc_commit(workspace_command.id());
let parent_id = parent.id();
let mut tx = workspace_command.start_transaction(format!("prev: {current} -> {parent_id}"));
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(());
}
let merged_tree = workspace_command.merge_commit_trees(tx.base_repo(), &parents);
// 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("@"))?;
Expand Down Expand Up @@ -3323,6 +3475,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),
Expand Down
66 changes: 66 additions & 0 deletions tests/test_next_prev_commands.rs
Original file line number Diff line number Diff line change
@@ -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();
}

0 comments on commit 8bc732c

Please sign in to comment.