Skip to content

Commit

Permalink
diffedit: add --restore-descendants flag
Browse files Browse the repository at this point in the history
  • Loading branch information
samueltardieu committed Sep 23, 2024
1 parent 25bd558 commit 5f249d0
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 6 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +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.

### Fixed bugs

* Update working copy before reporting changes. This prevents errors during reporting
Expand Down
28 changes: 23 additions & 5 deletions cli/src/commands/diffedit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ use crate::ui::Ui;
///
/// Edit the right side of the diff until it looks the way you want. Once you
/// close the editor, the revision specified with `-r` or `--to` will be
/// updated. Descendants will be rebased on top as usual, which may result in
/// conflicts.
/// updated. Unless `--restore-descendants` is used, descendants will be
/// rebased on top as usual, which may result in conflicts.
///
/// See `jj restore` if you want to move entire files from one revision to
/// another. See `jj squash -i` or `jj unsquash -i` if you instead want to move
Expand All @@ -64,6 +64,14 @@ pub(crate) struct DiffeditArgs {
/// Specify diff editor to be used
#[arg(long, value_name = "NAME")]
tool: Option<String>,
/// Preserve the content (not the diff) when rebasing descendants
///
/// When rebasing a descendant on top of the rewritten revision, its diff
/// compared to its parent(s) is normally preserved, i.e. the same way that
/// descendants are always rebased. This flag makes it so the content/state
/// is preserved instead of preserving the diff.
#[arg(long)]
restore_descendants: bool,
}

#[instrument(skip_all)]
Expand Down Expand Up @@ -119,13 +127,23 @@ don't make any changes, then the operation will be aborted.",
.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_rewritten, 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")?;
if num_rewritten > 0 {
writeln!(
formatter,
"Rebased {num_rewritten} descendant commits{extra_msg}"
)?;
}
}
tx.finish(ui, format!("edit commit {}", target_commit.id().hex()))?;
Expand Down
5 changes: 4 additions & 1 deletion cli/tests/[email protected]
Original file line number Diff line number Diff line change
Expand Up @@ -655,7 +655,7 @@ With the `--from` and/or `--to` options, starts a [diff editor] comparing the "f
[diff editor]: https://martinvonz.github.io/jj/latest/config/#editing-diffs
Edit the right side of the diff until it looks the way you want. Once you close the editor, the revision specified with `-r` or `--to` will be updated. Descendants will be rebased on top as usual, which may result in conflicts.
Edit the right side of the diff until it looks the way you want. Once you close the editor, the revision specified with `-r` or `--to` will be updated. Unless `--restore-descendants` is used, descendants will be rebased on top as usual, which may result in conflicts.
See `jj restore` if you want to move entire files from one revision to another. See `jj squash -i` or `jj unsquash -i` if you instead want to move changes into or out of the parent revision.
Expand All @@ -673,6 +673,9 @@ See `jj restore` if you want to move entire files from one revision to another.
Defaults to @ if --from is specified.
* `--tool <NAME>` — Specify diff editor to be used
* `--restore-descendants` — Preserve the content (not the diff) when rebasing descendants
When rebasing a descendant on top of the rewritten revision, its diff compared to its parent(s) is normally preserved, i.e. the same way that descendants are always rebased. This flag makes it so the content/state is preserved instead of preserving the diff.
Expand Down
118 changes: 118 additions & 0 deletions cli/tests/test_diffedit_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.

use std::path::Path;

use crate::common::escaped_fake_diff_editor_path;
use crate::common::TestEnvironment;

Expand Down Expand Up @@ -514,3 +516,119 @@ fn test_diffedit_old_restore_interactive_tests() {
+unrelated
"###);
}

#[test]
fn test_diffedit_restore_descendants() {
let mut 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");

std::fs::write(repo_path.join("file"), "println!(\"foo\")\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new"]);
std::fs::write(repo_path.join("file"), "println!(\"bar\")\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new"]);
std::fs::write(repo_path.join("file"), "println!(\"baz\");\n").unwrap();

let edit_script = test_env.set_up_fake_diff_editor();

// Add a ";" after the line with "bar". There should be no conflict.
std::fs::write(edit_script, "write file\nprintln!(\"bar\");\n").unwrap();
let (stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["diffedit", "-r", "@-", "--restore-descendants"],
);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r#"
Created rlvkpnrz 62b8c2ce (no description set)
Rebased 1 descendant commits (while preserving their content)
Working copy now at: kkmpptxz 321d1cd1 (no description set)
Parent commit : rlvkpnrz 62b8c2ce (no description set)
"#);
let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--git"]);
insta::assert_snapshot!(stdout, @r#"
diff --git a/file b/file
index 1a598a8fc9..7b6a85ab5a 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-println!("bar");
+println!("baz");
"#);
}

#[test]
fn test_diffedit_restore_descendants_with_multiple_parents() {
let mut 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");

std::fs::write(repo_path.join("file"), "println!(\"foo\")\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new"]);
std::fs::write(repo_path.join("file"), "println!(\"bar\")\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "tbm"]); // "to be modified"
test_env.jj_cmd_ok(&repo_path, &["new"]);
std::fs::write(repo_path.join("file"), "println!(\"baz\");\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["new", "zzzzz"]);
test_env.jj_cmd_ok(&repo_path, &["new", "@", "tbm"]);
std::fs::write(repo_path.join("file"), "println!(\"xyzzy\");\n").unwrap();
test_env.jj_cmd_ok(&repo_path, &["bookmark", "create", "xyzzy"]);

insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r#"
@ royxmykxtrkr -- f38df8938127 [xyzzy]
├─╮
○ │ mzvwutvlkqwt -- 7c376b2ea453
│ │ ○ zsuskulnrvyr -- 77193083222d
│ ├─╯
│ ○ rlvkpnrzqnoo -- 273bce8128c0 [tbm]
│ ○ qpvuntsmwlqt -- 5eee025d3098
├─╯
◆ zzzzzzzzzzzz -- 000000000000
"#);

let edit_script = test_env.set_up_fake_diff_editor();

std::fs::write(edit_script, "write file\nprintln!(\"bar\");\n").unwrap();
let (stdout, stderr) = test_env.jj_cmd_ok(
&repo_path,
&["diffedit", "-r", "tbm", "--restore-descendants"],
);
insta::assert_snapshot!(stdout, @"");
insta::assert_snapshot!(stderr, @r#"
Created rlvkpnrz 606df051 tbm | (no description set)
Rebased 2 descendant commits (while preserving their content)
Working copy now at: royxmykx 0a8f5fdf xyzzy | (no description set)
Parent commit : mzvwutvl 7c376b2e (empty) (no description set)
Parent commit : rlvkpnrz 606df051 tbm | (no description set)
"#);

insta::assert_snapshot!(get_log_output(&test_env, &repo_path), @r#"
@ royxmykxtrkr -- 0a8f5fdf0299 [xyzzy]
├─╮
○ │ mzvwutvlkqwt -- 7c376b2ea453
│ │ ○ zsuskulnrvyr -- 7682cc88fe29
│ ├─╯
│ ○ rlvkpnrzqnoo -- 606df0517465 [tbm]
│ ○ qpvuntsmwlqt -- 5eee025d3098
├─╯
◆ zzzzzzzzzzzz -- 000000000000
"#);

let stdout = test_env.jj_cmd_success(
&repo_path,
&["diff", "--git", "--from", "tbm", "--to", "xyzzy"],
);
insta::assert_snapshot!(stdout, @r#"
diff --git a/file b/file
index 1a598a8fc9..5a047f2b6b 100644
--- a/file
+++ b/file
@@ -1,1 +1,1 @@
-println!("bar");
+println!("xyzzy");
"#);
}

fn get_log_output(test_env: &TestEnvironment, repo_path: &Path) -> String {
let template = r#"change_id.short() ++ " -- " ++ commit_id.short() ++ " " ++ if(bookmarks, "[" ++ bookmarks ++ "]")"#;
test_env.jj_cmd_success(repo_path, &["log", "-T", template])
}
1 change: 1 addition & 0 deletions cli/tests/test_global_opts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,6 +602,7 @@ fn test_help() {
--from <FROM> Show changes from this revision
--to <TO> Edit changes in this revision
--tool <NAME> Specify diff editor to be used
--restore-descendants Preserve the content (not the diff) when rebasing descendants
-h, --help Print help (see more with '--help')
Global Options:
Expand Down

0 comments on commit 5f249d0

Please sign in to comment.