mirror of
https://github.com/martinvonz/jj.git
synced 2024-10-23 06:57:32 +00:00
rebase -s: add support for --insert-after
and --insert-before
options
This commit is contained in:
parent
f5e989d77b
commit
dbd9635b5e
4 changed files with 241 additions and 59 deletions
|
@ -200,6 +200,9 @@ Thanks to the people who made this release happen!
|
|||
|
||||
* When reconfiguring the author, warn that the working copy won't be updated
|
||||
|
||||
* `jj rebase -s` can now be used with the `--insert-after` and `--insert-before`
|
||||
options, like `jj rebase -r`.
|
||||
|
||||
### Fixed bugs
|
||||
|
||||
* Release binaries for Intel Macs have been restored. They were previously
|
||||
|
|
|
@ -169,26 +169,24 @@ pub(crate) struct RebaseArgs {
|
|||
/// The revision(s) to insert after (can be repeated to create a merge
|
||||
/// commit)
|
||||
///
|
||||
/// Only works with `-r`.
|
||||
/// Only works with `-r` and `-s`.
|
||||
#[arg(
|
||||
long,
|
||||
short = 'A',
|
||||
visible_alias = "after",
|
||||
conflicts_with = "destination",
|
||||
conflicts_with = "source",
|
||||
conflicts_with = "branch"
|
||||
)]
|
||||
insert_after: Vec<RevisionArg>,
|
||||
/// The revision(s) to insert before (can be repeated to create a merge
|
||||
/// commit)
|
||||
///
|
||||
/// Only works with `-r`.
|
||||
/// Only works with `-r` and `-s`.
|
||||
#[arg(
|
||||
long,
|
||||
short = 'B',
|
||||
visible_alias = "before",
|
||||
conflicts_with = "destination",
|
||||
conflicts_with = "source",
|
||||
conflicts_with = "branch"
|
||||
)]
|
||||
insert_before: Vec<RevisionArg>,
|
||||
|
@ -252,18 +250,14 @@ pub(crate) fn cmd_rebase(
|
|||
&rebase_options,
|
||||
)?;
|
||||
} else if !args.source.is_empty() {
|
||||
let new_parents = workspace_command
|
||||
.resolve_some_revsets_default_single(ui, &args.destination)?
|
||||
.into_iter()
|
||||
.collect_vec();
|
||||
let source_commits =
|
||||
workspace_command.resolve_some_revsets_default_single(ui, &args.source)?;
|
||||
rebase_descendants_transaction(
|
||||
rebase_source(
|
||||
ui,
|
||||
command.settings(),
|
||||
&mut workspace_command,
|
||||
new_parents,
|
||||
&source_commits,
|
||||
&args.source,
|
||||
&args.destination,
|
||||
&args.insert_after,
|
||||
&args.insert_before,
|
||||
&rebase_options,
|
||||
)?;
|
||||
} else {
|
||||
|
@ -334,6 +328,47 @@ fn rebase_revisions(
|
|||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn rebase_source(
|
||||
ui: &mut Ui,
|
||||
settings: &UserSettings,
|
||||
workspace_command: &mut WorkspaceCommandHelper,
|
||||
source: &[RevisionArg],
|
||||
destination: &[RevisionArg],
|
||||
insert_after: &[RevisionArg],
|
||||
insert_before: &[RevisionArg],
|
||||
rebase_options: &RebaseOptions,
|
||||
) -> Result<(), CommandError> {
|
||||
let source_commits = workspace_command
|
||||
.resolve_some_revsets_default_single(ui, source)?
|
||||
.into_iter()
|
||||
.collect_vec();
|
||||
workspace_command.check_rewritable(source_commits.iter().ids())?;
|
||||
|
||||
let (new_parents, new_children) = compute_rebase_destination(
|
||||
ui,
|
||||
workspace_command,
|
||||
destination,
|
||||
insert_after,
|
||||
insert_before,
|
||||
)?;
|
||||
if !destination.is_empty() && new_children.is_empty() {
|
||||
for commit in &source_commits {
|
||||
check_rebase_destinations(workspace_command.repo(), &new_parents, commit)?;
|
||||
}
|
||||
}
|
||||
|
||||
rebase_descendants_transaction(
|
||||
ui,
|
||||
settings,
|
||||
workspace_command,
|
||||
&new_parents.iter().ids().cloned().collect_vec(),
|
||||
&new_children,
|
||||
&source_commits,
|
||||
rebase_options,
|
||||
)
|
||||
}
|
||||
|
||||
fn rebase_branch(
|
||||
ui: &mut Ui,
|
||||
settings: &UserSettings,
|
||||
|
@ -342,28 +377,31 @@ fn rebase_branch(
|
|||
branch_commits: &IndexSet<Commit>,
|
||||
rebase_options: RebaseOptions,
|
||||
) -> Result<(), CommandError> {
|
||||
let parent_ids = new_parents
|
||||
.iter()
|
||||
.map(|commit| commit.id().clone())
|
||||
.collect_vec();
|
||||
let parent_ids = new_parents.iter().ids().cloned().collect_vec();
|
||||
let branch_commit_ids = branch_commits
|
||||
.iter()
|
||||
.map(|commit| commit.id().clone())
|
||||
.collect_vec();
|
||||
let roots_expression = RevsetExpression::commits(parent_ids)
|
||||
let roots_expression = RevsetExpression::commits(parent_ids.clone())
|
||||
.range(&RevsetExpression::commits(branch_commit_ids))
|
||||
.roots();
|
||||
let root_commits: IndexSet<_> = roots_expression
|
||||
let root_commits: Vec<_> = roots_expression
|
||||
.evaluate_programmatic(workspace_command.repo().as_ref())
|
||||
.unwrap()
|
||||
.iter()
|
||||
.commits(workspace_command.repo().store())
|
||||
.try_collect()?;
|
||||
workspace_command.check_rewritable(root_commits.iter().ids())?;
|
||||
for commit in &root_commits {
|
||||
check_rebase_destinations(workspace_command.repo(), &new_parents, commit)?;
|
||||
}
|
||||
|
||||
rebase_descendants_transaction(
|
||||
ui,
|
||||
settings,
|
||||
workspace_command,
|
||||
new_parents,
|
||||
&parent_ids,
|
||||
&[],
|
||||
&root_commits,
|
||||
&rebase_options,
|
||||
)
|
||||
|
@ -373,19 +411,15 @@ fn rebase_descendants_transaction(
|
|||
ui: &mut Ui,
|
||||
settings: &UserSettings,
|
||||
workspace_command: &mut WorkspaceCommandHelper,
|
||||
new_parents: Vec<Commit>,
|
||||
target_roots: &IndexSet<Commit>,
|
||||
new_parent_ids: &[CommitId],
|
||||
new_children: &[Commit],
|
||||
target_roots: &[Commit],
|
||||
rebase_options: &RebaseOptions,
|
||||
) -> Result<(), CommandError> {
|
||||
if target_roots.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
workspace_command.check_rewritable(target_roots.iter().ids())?;
|
||||
for commit in target_roots.iter() {
|
||||
check_rebase_destinations(workspace_command.repo(), &new_parents, commit)?;
|
||||
}
|
||||
|
||||
let mut tx = workspace_command.start_transaction();
|
||||
let tx_description = if target_roots.len() == 1 {
|
||||
format!(
|
||||
|
@ -406,8 +440,6 @@ fn rebase_descendants_transaction(
|
|||
.iter()
|
||||
.commits(tx.repo().store())
|
||||
.try_collect()?;
|
||||
let new_parent_ids = new_parents.iter().ids().cloned().collect_vec();
|
||||
let new_children: [Commit; 0] = [];
|
||||
let target_roots = target_roots.iter().ids().cloned().collect_vec();
|
||||
|
||||
let MoveCommitsStats {
|
||||
|
@ -418,15 +450,12 @@ fn rebase_descendants_transaction(
|
|||
} = move_commits(
|
||||
settings,
|
||||
tx.repo_mut(),
|
||||
&new_parent_ids,
|
||||
&new_children,
|
||||
new_parent_ids,
|
||||
new_children,
|
||||
&target_commits,
|
||||
Some(&target_roots),
|
||||
rebase_options,
|
||||
)?;
|
||||
// All the descendants of the roots should be part of `target_commits`, so
|
||||
// `num_rebased_descendants` should be 0.
|
||||
debug_assert_eq!(num_rebased_descendants, 0);
|
||||
|
||||
if num_skipped_rebases > 0 {
|
||||
writeln!(
|
||||
|
|
|
@ -1755,10 +1755,10 @@ commit. This is true in general; it is not specific to this command.
|
|||
* `-d`, `--destination <DESTINATION>` — The revision(s) to rebase onto (can be repeated to create a merge commit)
|
||||
* `-A`, `--insert-after <INSERT_AFTER>` — The revision(s) to insert after (can be repeated to create a merge commit)
|
||||
|
||||
Only works with `-r`.
|
||||
Only works with `-r` and `-s`.
|
||||
* `-B`, `--insert-before <INSERT_BEFORE>` — The revision(s) to insert before (can be repeated to create a merge commit)
|
||||
|
||||
Only works with `-r`.
|
||||
Only works with `-r` and `-s`.
|
||||
* `--skip-emptied` — If true, when rebasing would produce an empty commit, the commit is abandoned. It will not be abandoned if it was already empty before the rebase. Will never skip merge commits with multiple non-empty parents
|
||||
|
||||
|
||||
|
|
|
@ -96,16 +96,6 @@ fn test_rebase_invalid() {
|
|||
For more information, try '--help'.
|
||||
"###);
|
||||
|
||||
// -s with --after
|
||||
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["rebase", "-s", "a", "--after", "b"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
error: the argument '--source <SOURCE>' cannot be used with '--insert-after <INSERT_AFTER>'
|
||||
|
||||
Usage: jj rebase --source <SOURCE> <--destination <DESTINATION>|--insert-after <INSERT_AFTER>|--insert-before <INSERT_BEFORE>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
|
||||
// -b with --after
|
||||
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["rebase", "-b", "a", "--after", "b"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
|
@ -129,16 +119,6 @@ fn test_rebase_invalid() {
|
|||
For more information, try '--help'.
|
||||
"###);
|
||||
|
||||
// -s with --before
|
||||
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["rebase", "-s", "a", "--before", "b"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
error: the argument '--source <SOURCE>' cannot be used with '--insert-before <INSERT_BEFORE>'
|
||||
|
||||
Usage: jj rebase --source <SOURCE> <--destination <DESTINATION>|--insert-after <INSERT_AFTER>|--insert-before <INSERT_BEFORE>>
|
||||
|
||||
For more information, try '--help'.
|
||||
"###);
|
||||
|
||||
// -b with --before
|
||||
let stderr = test_env.jj_cmd_cli_error(&repo_path, &["rebase", "-b", "a", "--before", "b"]);
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
|
@ -1300,7 +1280,7 @@ fn test_rebase_with_child_and_descendant_bug_2600() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_rebase_revisions_after() {
|
||||
fn test_rebase_after() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
|
||||
let repo_path = test_env.env_root().join("repo");
|
||||
|
@ -1701,6 +1681,62 @@ fn test_rebase_revisions_after() {
|
|||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// `rebase -s` of commit "c" and its descendants after itself should be a no-op.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["rebase", "-s", "c", "--after", "c"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Skipped rebase of 4 commits that were already in place
|
||||
Nothing changed.
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ f: e
|
||||
○ e: c
|
||||
│ ○ d: c
|
||||
├─╯
|
||||
○ c: b2 b4
|
||||
├─╮
|
||||
│ ○ b4: b3
|
||||
│ ○ b3: a
|
||||
○ │ b2: b1
|
||||
○ │ b1: a
|
||||
├─╯
|
||||
○ a
|
||||
◆
|
||||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// `rebase -s` of a commit and its descendants after multiple commits.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(
|
||||
&repo_path,
|
||||
&["rebase", "-s", "c", "--after", "b1", "--after", "b3"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Rebased 4 commits
|
||||
Rebased 2 descendant commits
|
||||
Working copy now at: xznxytkn a4ace41c f | f
|
||||
Parent commit : nkmrtpmo c7744d08 e | e
|
||||
Added 0 files, modified 0 files, removed 2 files
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
○ b4: d f
|
||||
├─╮
|
||||
│ │ ○ b2: d f
|
||||
╭─┬─╯
|
||||
│ @ f: e
|
||||
│ ○ e: c
|
||||
○ │ d: c
|
||||
├─╯
|
||||
○ c: b1 b3
|
||||
├─╮
|
||||
│ ○ b3: a
|
||||
○ │ b1: a
|
||||
├─╯
|
||||
○ a
|
||||
◆
|
||||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// Should error if a loop will be created.
|
||||
let stderr = test_env.jj_cmd_failure(
|
||||
&repo_path,
|
||||
|
@ -1712,7 +1748,7 @@ fn test_rebase_revisions_after() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_rebase_revisions_before() {
|
||||
fn test_rebase_before() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
|
||||
let repo_path = test_env.env_root().join("repo");
|
||||
|
@ -2122,6 +2158,92 @@ fn test_rebase_revisions_before() {
|
|||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// Rebase a subgraph before the parents of one of the commits in the subgraph.
|
||||
// "c" had parents "b2" and "b4", but no longer has "b4" as a parent since
|
||||
// "b4" would be a descendant of "c" after the rebase.
|
||||
let (stdout, stderr) =
|
||||
test_env.jj_cmd_ok(&repo_path, &["rebase", "-r", "b2::d", "--before", "a"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Rebased 3 commits onto destination
|
||||
Rebased 6 descendant commits
|
||||
Working copy now at: xznxytkn 308a31e9 f | f
|
||||
Parent commit : nkmrtpmo 538444a5 e | e
|
||||
Added 1 files, modified 0 files, removed 0 files
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ f: e
|
||||
○ e: b1 b4
|
||||
├─╮
|
||||
│ ○ b4: b3
|
||||
│ ○ b3: a
|
||||
○ │ b1: a
|
||||
├─╯
|
||||
○ a: d
|
||||
○ d: c
|
||||
○ c: b2
|
||||
○ b2
|
||||
◆
|
||||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// `rebase -s` of commit "c" and its descendants before itself should be a
|
||||
// no-op.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(&repo_path, &["rebase", "-s", "c", "--before", "c"]);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Skipped rebase of 4 commits that were already in place
|
||||
Nothing changed.
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
@ f: e
|
||||
○ e: c
|
||||
│ ○ d: c
|
||||
├─╯
|
||||
○ c: b2 b4
|
||||
├─╮
|
||||
│ ○ b4: b3
|
||||
│ ○ b3: a
|
||||
○ │ b2: b1
|
||||
○ │ b1: a
|
||||
├─╯
|
||||
○ a
|
||||
◆
|
||||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// `rebase -s` of a commit and its descendants before multiple commits.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(
|
||||
&repo_path,
|
||||
&["rebase", "-s", "c", "--before", "b2", "--before", "b4"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Rebased 4 commits
|
||||
Rebased 2 descendant commits
|
||||
Working copy now at: xznxytkn 84704387 f | f
|
||||
Parent commit : nkmrtpmo cff61821 e | e
|
||||
Added 0 files, modified 0 files, removed 2 files
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
○ b4: d f
|
||||
├─╮
|
||||
│ │ ○ b2: d f
|
||||
╭─┬─╯
|
||||
│ @ f: e
|
||||
│ ○ e: c
|
||||
○ │ d: c
|
||||
├─╯
|
||||
○ c: b1 b3
|
||||
├─╮
|
||||
│ ○ b3: a
|
||||
○ │ b1: a
|
||||
├─╯
|
||||
○ a
|
||||
◆
|
||||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// Should error if a loop will be created.
|
||||
let stderr = test_env.jj_cmd_failure(
|
||||
&repo_path,
|
||||
|
@ -2133,7 +2255,7 @@ fn test_rebase_revisions_before() {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_rebase_revisions_after_before() {
|
||||
fn test_rebase_after_before() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.jj_cmd_ok(test_env.env_root(), &["git", "init", "repo"]);
|
||||
let repo_path = test_env.env_root().join("repo");
|
||||
|
@ -2288,6 +2410,34 @@ fn test_rebase_revisions_after_before() {
|
|||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// `rebase -s` of a commit and its descendants.
|
||||
let (stdout, stderr) = test_env.jj_cmd_ok(
|
||||
&repo_path,
|
||||
&["rebase", "-s", "c", "--before", "b1", "--after", "b2"],
|
||||
);
|
||||
insta::assert_snapshot!(stdout, @"");
|
||||
insta::assert_snapshot!(stderr, @r###"
|
||||
Rebased 4 commits
|
||||
Rebased 1 descendant commits
|
||||
Working copy now at: lylxulpl 108f0202 f | f
|
||||
Parent commit : kmkuslsw 52245d71 e | e
|
||||
Added 0 files, modified 0 files, removed 1 files
|
||||
"###);
|
||||
insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r###"
|
||||
○ b1: a d f
|
||||
├─┬─╮
|
||||
│ │ @ f: e
|
||||
│ │ ○ e: c
|
||||
│ ○ │ d: c
|
||||
│ ├─╯
|
||||
│ ○ c: b2
|
||||
│ ○ b2: a
|
||||
├─╯
|
||||
○ a
|
||||
◆
|
||||
"###);
|
||||
test_env.jj_cmd_ok(&repo_path, &["op", "restore", &setup_opid]);
|
||||
|
||||
// Should error if a loop will be created.
|
||||
let stderr = test_env.jj_cmd_failure(
|
||||
&repo_path,
|
||||
|
|
Loading…
Reference in a new issue