Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,12 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
tags. These keywords may be useful in non-colocated Git repositories where
local and exported `@git` tags can point to different revisions.

* `jj prev/next --no-edit` now generates an error if the working-copy has some
children.

* `jj prev` and `jj next` have gained a `--rebase` flag to move the current changes
while moving to the previous or next revision.

### Fixed bugs

* `jj metaedit --author-timestamp` twice with the same value no longer
Expand Down
6 changes: 6 additions & 0 deletions cli/src/commands/next.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,11 @@ pub(crate) struct NextArgs {
/// will negate `ui.movement.edit = true`
#[arg(long, short, conflicts_with = "edit")]
no_edit: bool,
/// Instead of creating a new working-copy commit on top of the target
/// commit (like `jj new`), move the working-copy on top of the target
/// commit (like `jj rebase`)
#[arg(long, short, conflicts_with = "edit")]
rebase: bool,
/// Jump to the next conflicted descendant
#[arg(long, conflicts_with = "offset")]
conflict: bool,
Expand All @@ -80,6 +85,7 @@ impl From<&NextArgs> for MovementArgs {
edit: val.edit,
no_edit: val.no_edit,
conflict: val.conflict,
rebase: val.rebase,
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions cli/src/commands/prev.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ pub(crate) struct PrevArgs {
/// will negate `ui.movement.edit = true`
#[arg(long, short, conflicts_with = "edit")]
no_edit: bool,
/// Instead of creating a new working-copy commit on top of the target
/// commit (like `jj new`), move the working-copy on top of the target
/// commit (like `jj rebase`)
#[arg(long, short, conflicts_with = "edit")]
rebase: bool,
/// Jump to the previous conflicted ancestor
#[arg(long, conflicts_with = "offset")]
conflict: bool,
Expand All @@ -76,6 +81,7 @@ impl From<&PrevArgs> for MovementArgs {
edit: val.edit,
no_edit: val.no_edit,
conflict: val.conflict,
rebase: val.rebase,
}
}
}
Expand Down
73 changes: 61 additions & 12 deletions cli/src/movement_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
// limitations under the License.

use std::io::Write as _;
use std::iter::once;
use std::sync::Arc;

use itertools::Itertools as _;
Expand All @@ -23,12 +24,17 @@ use jj_lib::revset::ResolvedRevsetExpression;
use jj_lib::revset::RevsetExpression;
use jj_lib::revset::RevsetFilterPredicate;
use jj_lib::revset::RevsetIteratorExt as _;
use jj_lib::rewrite::MoveCommitsLocation;
use jj_lib::rewrite::MoveCommitsTarget;
use jj_lib::rewrite::RebaseOptions;
use jj_lib::rewrite::move_commits;

use crate::cli_util::CommandHelper;
use crate::cli_util::WorkspaceCommandHelper;
use crate::cli_util::short_commit_hash;
use crate::command_error::CommandError;
use crate::command_error::user_error;
use crate::command_error::user_error_with_hint;
use crate::ui::Ui;

#[derive(Clone, Debug, Eq, PartialEq)]
Expand All @@ -37,6 +43,7 @@ pub(crate) struct MovementArgs {
pub edit: bool,
pub no_edit: bool,
pub conflict: bool,
pub rebase: bool,
}

#[derive(Clone, Debug, Eq, PartialEq)]
Expand Down Expand Up @@ -157,6 +164,21 @@ fn get_target_commit(
args: &MovementArgsInternal,
) -> Result<Commit, CommandError> {
let wc_revset = RevsetExpression::commit(working_commit_id.clone());

// If we're not editing, the working-copy shouldn't have any children
if !args.should_edit
&& !workspace_command
.repo()
.view()
.heads()
.contains(working_commit_id)
{
return Err(user_error_with_hint(
"The working copy must not have any children",
"Create a new commit on top of this one or use `--edit`",
));
}

// If we're editing, start at the working-copy commit. Otherwise, start from
// its direct parent(s).
let start_revset = if args.should_edit {
Expand Down Expand Up @@ -234,34 +256,61 @@ pub(crate) fn move_to_commit(

let current_wc_id = workspace_command
.get_wc_commit_id()
.cloned()
.ok_or_else(|| user_error("This command requires a working copy"))?;

let config_edit_flag = workspace_command.settings().get_bool("ui.movement.edit")?;
let args = MovementArgsInternal {
should_edit: args.edit || (!args.no_edit && config_edit_flag),
let should_edit = args.edit || (!args.no_edit && config_edit_flag);
if should_edit && args.rebase {
return Err(user_error_with_hint(
"`--keep` can't be used with `ui.movement.edit`",
"Use `--no-edit` or set `ui.movement.edit` to `false`",
));
}
let args_internal = MovementArgsInternal {
should_edit,
offset: args.offset,
conflict: args.conflict,
};

let target = get_target_commit(ui, &workspace_command, direction, current_wc_id, &args)?;
let current_short = short_commit_hash(current_wc_id);
let target = get_target_commit(
ui,
&workspace_command,
direction,
&current_wc_id,
&args_internal,
)?;
let current_short = short_commit_hash(&current_wc_id);
let target_short = short_commit_hash(target.id());
let cmd = direction.cmd();
// We're editing, just move to the target commit.
if args.should_edit {
// We're editing, the target must be rewritable.
if args.rebase {
workspace_command.check_rewritable(once(&current_wc_id))?;
let mut tx = workspace_command.start_transaction();
move_commits(
tx.repo_mut(),
&MoveCommitsLocation {
new_parent_ids: vec![target.id().clone()],
new_child_ids: vec![],
target: MoveCommitsTarget::Commits(vec![current_wc_id]),
},
&RebaseOptions::default(),
)?;
tx.finish(ui, format!("{cmd}: {current_short} -> {target_short}"))?;
} else if args_internal.should_edit {
// We're editing, just move to the target commit.
// The target must be rewritable.
workspace_command.check_rewritable([target.id()])?;
let mut tx = workspace_command.start_transaction();
tx.edit(&target)?;
tx.finish(
ui,
format!("{cmd}: {current_short} -> editing {target_short}"),
)?;
return Ok(());
} else {
let mut tx = workspace_command.start_transaction();
// Move the working-copy commit to the new parent.
tx.check_out(&target)?;
tx.finish(ui, format!("{cmd}: {current_short} -> {target_short}"))?;
}
let mut tx = workspace_command.start_transaction();
// Move the working-copy commit to the new parent.
tx.check_out(&target)?;
tx.finish(ui, format!("{cmd}: {current_short} -> {target_short}"))?;
Ok(())
}
2 changes: 2 additions & 0 deletions cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -1867,6 +1867,7 @@ B => @
* `-n`, `--no-edit` — The inverse of `--edit`

Takes precedence over config in `ui.movement.edit`; i.e. will negate `ui.movement.edit = true`
* `-r`, `--rebase` — Instead of creating a new working-copy commit on top of the target commit (like `jj new`), move the working-copy on top of the target commit (like `jj rebase`)
* `--conflict` — Jump to the next conflicted descendant


Expand Down Expand Up @@ -2183,6 +2184,7 @@ A A
* `-n`, `--no-edit` — The inverse of `--edit`

Takes precedence over config in `ui.movement.edit`; i.e. will negate `ui.movement.edit = true`
* `-r`, `--rebase` — Instead of creating a new working-copy commit on top of the target commit (like `jj new`), move the working-copy on top of the target commit (like `jj rebase`)
* `--conflict` — Jump to the previous conflicted ancestor


Expand Down
18 changes: 18 additions & 0 deletions cli/tests/test_immutable_commits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,24 @@ fn test_rewrite_immutable_commands() {
[EOF]
[exit status: 1]
"#);
// next
work_dir
.run_jj(["edit", "--ignore-immutable", "mzvwutvl"])
.success(); // move to an immutable commit
let output = work_dir.run_jj(["next", "--rebase"]);
insta::assert_snapshot!(output, @r#"
------- stderr -------
Error: Commit 4397373a0991 is immutable
Hint: Could not modify commit: mzvwutvl 4397373a main | (conflict) merge
Hint: Immutable commits are used to protect shared history.
Hint: For more information, see:
- https://jj-vcs.github.io/jj/latest/config/#set-of-immutable-commits
- `jj help -k config`, "Set of immutable commits"
Hint: This operation would rewrite 1 immutable commits.
[EOF]
[exit status: 1]
"#);
work_dir.run_jj(["undo"]).success(); // go back to the pre-edit state
// parallelize
let output = work_dir.run_jj(["parallelize", "description(b)", "main"]);
insta::assert_snapshot!(output, @r#"
Expand Down
Loading