cli: add interdiff command

Add the `jj interdiff` command for comparing only the diffs of commits.
Its args are identical to that of `jj diff`, minus `--revision` (because
interdiff always requires two commits).

Like `jj obslog -p`, Changes introduced by intervening commits are
ignored by rebasing `--from` onto `--to` 's parents.
This commit is contained in:
Glen Choo 2022-08-30 11:47:38 -07:00
parent ed415c17b0
commit 468418f1ce
3 changed files with 214 additions and 0 deletions

View file

@ -56,6 +56,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### New features ### New features
* The new `jj interdiff` command compares the changes in commits, ignoring
changes from intervening commits.
* `jj rebase` now accepts a `--branch/-b <revision>` argument, which can be used * `jj rebase` now accepts a `--branch/-b <revision>` argument, which can be used
instead of `-r` or `-s` to specify which commits to rebase. It will rebase the instead of `-r` or `-s` to specify which commits to rebase. It will rebase the
whole branch, relative to the destination. The default mode has changed from whole branch, relative to the destination. The default mode has changed from

View file

@ -1101,6 +1101,7 @@ enum Commands {
Status(StatusArgs), Status(StatusArgs),
Log(LogArgs), Log(LogArgs),
Obslog(ObslogArgs), Obslog(ObslogArgs),
Interdiff(InterdiffArgs),
Describe(DescribeArgs), Describe(DescribeArgs),
Close(CloseArgs), Close(CloseArgs),
Open(OpenArgs), Open(OpenArgs),
@ -1323,6 +1324,26 @@ struct ObslogArgs {
diff_format: DiffFormatArgs, diff_format: DiffFormatArgs,
} }
/// Compare the changes of two commits
///
/// This excludes changes from other commits by temporarily rebasing `--from`
/// onto `--to`'s parents. If you wish to compare the same change across
/// versions, consider `jj obslog -p` instead.
#[derive(clap::Args, Clone, Debug)]
#[clap(group(ArgGroup::new("to_diff").args(&["from", "to"]).multiple(true).required(true)))]
struct InterdiffArgs {
/// Show changes from this revision
#[clap(long)]
from: Option<String>,
/// Show changes to this revision
#[clap(long)]
to: Option<String>,
/// Restrict the diff to these paths
paths: Vec<String>,
#[clap(flatten)]
format: DiffFormatArgs,
}
/// Edit the change description /// Edit the change description
/// ///
/// Starts an editor to let you edit the description of a change. The editor /// Starts an editor to let you edit the description of a change. The editor
@ -3235,6 +3256,27 @@ fn show_predecessor_patch(
show_diff(formatter, workspace_command, diff_iterator, diff_format) show_diff(formatter, workspace_command, diff_iterator, diff_format)
} }
fn cmd_interdiff(
ui: &mut Ui,
command: &CommandHelper,
args: &InterdiffArgs,
) -> Result<(), CommandError> {
let workspace_command = command.workspace_helper(ui)?;
let from = workspace_command.resolve_single_rev(args.from.as_deref().unwrap_or("@"))?;
let to = workspace_command.resolve_single_rev(args.to.as_deref().unwrap_or("@"))?;
let from_tree = rebase_to_dest_parent(&workspace_command, &from, &to)?;
let workspace_root = workspace_command.workspace_root();
let matcher = matcher_from_values(ui, workspace_root, &args.paths)?;
let diff_iterator = from_tree.diff(&to.tree(), matcher.as_ref());
show_diff(
ui.stdout_formatter().as_mut(),
&workspace_command,
diff_iterator,
diff_format_for(ui, &args.format),
)
}
fn rebase_to_dest_parent( fn rebase_to_dest_parent(
workspace_command: &WorkspaceCommandHelper, workspace_command: &WorkspaceCommandHelper,
source: &Commit, source: &Commit,
@ -5499,6 +5541,7 @@ where
Commands::Show(sub_args) => cmd_show(ui, &command_helper, sub_args), Commands::Show(sub_args) => cmd_show(ui, &command_helper, sub_args),
Commands::Status(sub_args) => cmd_status(ui, &command_helper, sub_args), Commands::Status(sub_args) => cmd_status(ui, &command_helper, sub_args),
Commands::Log(sub_args) => cmd_log(ui, &command_helper, sub_args), Commands::Log(sub_args) => cmd_log(ui, &command_helper, sub_args),
Commands::Interdiff(sub_args) => cmd_interdiff(ui, &command_helper, sub_args),
Commands::Obslog(sub_args) => cmd_obslog(ui, &command_helper, sub_args), Commands::Obslog(sub_args) => cmd_obslog(ui, &command_helper, sub_args),
Commands::Describe(sub_args) => cmd_describe(ui, &command_helper, sub_args), Commands::Describe(sub_args) => cmd_describe(ui, &command_helper, sub_args),
Commands::Close(sub_args) => cmd_close(ui, &command_helper, sub_args), Commands::Close(sub_args) => cmd_close(ui, &command_helper, sub_args),

View file

@ -0,0 +1,168 @@
// Copyright 2022 Google LLC
//
// 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.
use common::TestEnvironment;
pub mod common;
#[test]
fn test_interdiff_basic() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file1"), "foo\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file2"), "foo\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["branch", "create", "left"]);
test_env.jj_cmd_success(&repo_path, &["checkout", "root"]);
std::fs::write(repo_path.join("file3"), "foo\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file2"), "foo\nbar\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["branch", "create", "right"]);
// implicit --to
let stdout = test_env.jj_cmd_success(&repo_path, &["interdiff", "--from", "left"]);
insta::assert_snapshot!(stdout, @r###"
Modified regular file file2:
1 1: foo
2: bar
"###);
// explicit --to
test_env.jj_cmd_success(&repo_path, &["checkout", "@-"]);
let stdout = test_env.jj_cmd_success(
&repo_path,
&["interdiff", "--from", "left", "--to", "right"],
);
insta::assert_snapshot!(stdout, @r###"
Modified regular file file2:
1 1: foo
2: bar
"###);
test_env.jj_cmd_success(&repo_path, &["undo"]);
// formats specifiers
let stdout = test_env.jj_cmd_success(
&repo_path,
&["interdiff", "--from", "left", "--to", "right", "-s"],
);
insta::assert_snapshot!(stdout, @r###"
M file2
"###);
let stdout = test_env.jj_cmd_success(
&repo_path,
&["interdiff", "--from", "left", "--to", "right", "--git"],
);
insta::assert_snapshot!(stdout, @r###"
diff --git a/file2 b/file2
index 257cc5642c...3bd1f0e297 100644
--- a/file2
+++ b/file2
@@ -1,1 +1,2 @@
foo
+bar
"###);
}
#[test]
fn test_interdiff_paths() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file1"), "foo\n").unwrap();
std::fs::write(repo_path.join("file2"), "foo\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file1"), "bar\n").unwrap();
std::fs::write(repo_path.join("file2"), "bar\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["branch", "create", "left"]);
test_env.jj_cmd_success(&repo_path, &["checkout", "root"]);
std::fs::write(repo_path.join("file1"), "foo\n").unwrap();
std::fs::write(repo_path.join("file2"), "foo\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file1"), "baz\n").unwrap();
std::fs::write(repo_path.join("file2"), "baz\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["branch", "create", "right"]);
let stdout = test_env.jj_cmd_success(
&repo_path,
&["interdiff", "--from", "left", "--to", "right", "file1"],
);
insta::assert_snapshot!(stdout, @r###"
Modified regular file file1:
1 1: barbaz
"###);
let stdout = test_env.jj_cmd_success(
&repo_path,
&[
"interdiff",
"--from",
"left",
"--to",
"right",
"file1",
"file2",
],
);
insta::assert_snapshot!(stdout, @r###"
Modified regular file file1:
1 1: barbaz
Modified regular file file2:
1 1: barbaz
"###);
}
#[test]
fn test_interdiff_conflicting() {
let test_env = TestEnvironment::default();
test_env.jj_cmd_success(test_env.env_root(), &["init", "repo", "--git"]);
let repo_path = test_env.env_root().join("repo");
std::fs::write(repo_path.join("file"), "foo\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file"), "bar\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["branch", "create", "left"]);
test_env.jj_cmd_success(&repo_path, &["checkout", "root"]);
std::fs::write(repo_path.join("file"), "abc\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["new"]);
std::fs::write(repo_path.join("file"), "def\n").unwrap();
test_env.jj_cmd_success(&repo_path, &["branch", "create", "right"]);
let stdout = test_env.jj_cmd_success(
&repo_path,
&["interdiff", "--from", "left", "--to", "right", "--git"],
);
insta::assert_snapshot!(stdout, @r###"
diff --git a/file b/file
index f845ab93f0...24c5735c3e 100644
--- a/file
+++ b/file
@@ -1,8 +1,1 @@
-<<<<<<<
--------
-+++++++
--foo
-+abc
-+++++++
-bar
->>>>>>>
+def
"###);
}