diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e535e5de0..15950ea314 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,11 +69,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). * `jj commit` and `jj describe` now accept `--author` option allowing to quickly change author of given commit. -* `jj diffedit` now accepts a `--restore-descendants` flag. When used, - descendants of the edited commit will keep their original content. - -* `jj abandon` now accepts a `--restore-descendants` flag. When used, - descendants of the abandoned commits will keep their original content. +* `jj diffedit`, `jj abandon`, and `jj restore` now accept a `--restore-descendants` + flag. When used, descendants of the edited or deleted commits will keep their original + content. ### Fixed bugs diff --git a/cli/src/commands/restore.rs b/cli/src/commands/restore.rs index 716dc8569d..6965c91350 100644 --- a/cli/src/commands/restore.rs +++ b/cli/src/commands/restore.rs @@ -70,6 +70,9 @@ pub(crate) struct RestoreArgs { /// the user might not even realize something went wrong. #[arg(long, short, hide = true)] revision: Option, + /// Preserve the content (not the diff) when rebasing descendants + #[arg(long)] + restore_descendants: bool, } #[instrument(skip_all)] @@ -116,13 +119,23 @@ pub(crate) fn cmd_restore( .write()?; // rebase_descendants early; otherwise `new_commit` would always have // a conflicted change id at this point. - let num_rebased = tx.repo_mut().rebase_descendants(command.settings())?; + let (num_rebased, extra_msg) = if args.restore_descendants { + ( + tx.repo_mut().reparent_descendants(command.settings())?, + " (while preserving their content)", + ) + } else { + (tx.repo_mut().rebase_descendants(command.settings())?, "") + }; if let Some(mut formatter) = ui.status_formatter() { write!(formatter, "Created ")?; tx.write_commit_summary(formatter.as_mut(), &new_commit)?; writeln!(formatter)?; if num_rebased > 0 { - writeln!(formatter, "Rebased {num_rebased} descendant commits")?; + writeln!( + formatter, + "Rebased {num_rebased} descendant commits{extra_msg}" + )?; } } tx.finish(ui, format!("restore into commit {}", to_commit.id().hex()))?; diff --git a/cli/tests/cli-reference@.md.snap b/cli/tests/cli-reference@.md.snap index c379572fe9..a96105336f 100644 --- a/cli/tests/cli-reference@.md.snap +++ b/cli/tests/cli-reference@.md.snap @@ -1784,6 +1784,7 @@ See `jj diffedit` if you'd like to restore portions of files rather than entire This undoes the changes that can be seen with `jj diff -r REVISION`. If `REVISION` only has a single parent, this option is equivalent to `jj restore --to REVISION --from REVISION-`. The default behavior of `jj restore` is equivalent to `jj restore --changes-in @`. +* `--restore-descendants` — Preserve the content (not the diff) when rebasing descendants diff --git a/cli/tests/test_restore_command.rs b/cli/tests/test_restore_command.rs index 92a5571402..0ab6cb1ad3 100644 --- a/cli/tests/test_restore_command.rs +++ b/cli/tests/test_restore_command.rs @@ -261,6 +261,87 @@ fn test_restore_conflicted_merge() { "###); } +#[test] +fn test_restore_restore_descendants() { + 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"); + + create_commit(&test_env, &repo_path, "base", &[], &[("file", "base\n")]); + create_commit(&test_env, &repo_path, "a", &["base"], &[("file", "a\n")]); + create_commit( + &test_env, + &repo_path, + "b", + &["base"], + &[("file", "b\n"), ("file2", "b\n")], + ); + create_commit( + &test_env, + &repo_path, + "ab", + &["a", "b"], + &[("file", "ab\n")], + ); + // Test the setup + insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r#" + @ ab + ├─╮ + │ ○ b + ○ │ a + ├─╯ + ○ base + ◆ + "#); + insta::assert_snapshot!( + std::fs::read_to_string(repo_path.join("file")).unwrap(), @r#" + ab + "#); + + // Commit "b" was not supposed to modify "file", restore it from its parent + // while preserving its child commit content. + let (stdout, stderr) = test_env.jj_cmd_ok( + &repo_path, + &["restore", "-c", "b", "file", "--restore-descendants"], + ); + insta::assert_snapshot!(stdout, @""); + insta::assert_snapshot!(stderr, @r#" + Created royxmykx 3fd5aa05 b | b + Rebased 1 descendant commits (while preserving their content) + Working copy now at: vruxwmqv bf5491a0 ab | ab + Parent commit : zsuskuln aa493daf a | a + Parent commit : royxmykx 3fd5aa05 b | b + "#); + + // Check that "a", "b", and "ab" have their expected content by diffing them. + // "ab" must have kept its content. + insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--from=a", "--to=ab", "--git"]), @r#" + diff --git a/file b/file + index 7898192261..81bf396956 100644 + --- a/file + +++ b/file + @@ -1,1 +1,1 @@ + -a + +ab + diff --git a/file2 b/file2 + new file mode 100644 + index 0000000000..6178079822 + --- /dev/null + +++ b/file2 + @@ -1,0 +1,1 @@ + +b + "#); + insta::assert_snapshot!(test_env.jj_cmd_success(&repo_path, &["diff", "--from=b", "--to=ab", "--git"]), @r#" + diff --git a/file b/file + index df967b96a5..81bf396956 100644 + --- a/file + +++ b/file + @@ -1,1 +1,1 @@ + -base + +ab + "#); +} + fn create_commit( test_env: &TestEnvironment, repo_path: &Path,