mirror of
https://github.com/martinvonz/jj.git
synced 2025-05-31 23:25:09 +00:00
cli: diff: support multiple revisions to -r
This commit is contained in:
parent
f862d89143
commit
dc7216d73a
@ -38,6 +38,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
### New features
|
||||
|
||||
* `jj diff -r` now allows multiple revisions (as long as there are no gaps in
|
||||
the revset), such as `jj diff -r 'mutable()'`.
|
||||
|
||||
* The 'how to resolve conflicts' hint that is shown when conflicts appear can
|
||||
be hidden by setting `hints.resolving-conflicts = false`.
|
||||
|
||||
|
@ -14,6 +14,7 @@
|
||||
|
||||
use clap_complete::ArgValueCandidates;
|
||||
use clap_complete::ArgValueCompleter;
|
||||
use indexmap::IndexSet;
|
||||
use itertools::Itertools;
|
||||
use jj_lib::copies::CopyRecords;
|
||||
use jj_lib::repo::Repo;
|
||||
@ -21,8 +22,10 @@ use jj_lib::rewrite::merge_commit_trees;
|
||||
use tracing::instrument;
|
||||
|
||||
use crate::cli_util::print_unmatched_explicit_paths;
|
||||
use crate::cli_util::short_commit_hash;
|
||||
use crate::cli_util::CommandHelper;
|
||||
use crate::cli_util::RevisionArg;
|
||||
use crate::command_error::user_error_with_hint;
|
||||
use crate::command_error::CommandError;
|
||||
use crate::complete;
|
||||
use crate::diff_util::get_copy_records;
|
||||
@ -44,23 +47,30 @@ use crate::ui::Ui;
|
||||
#[command(mut_arg("ignore_all_space", |a| a.short('w')))]
|
||||
#[command(mut_arg("ignore_space_change", |a| a.short('b')))]
|
||||
pub(crate) struct DiffArgs {
|
||||
/// Show changes in this revision, compared to its parent(s)
|
||||
/// Show changes in these revisions
|
||||
///
|
||||
/// If the revision is a merge commit, this shows changes *from* the
|
||||
/// If there are multiple revisions, then then total diff for all of them
|
||||
/// will be shown. For example, if you have a linear chain of revisions
|
||||
/// A..D, then `jj diff -r B::D` equals `jj diff --from A --to D`. Multiple
|
||||
/// heads and/or roots are supported, but gaps in the revset are not
|
||||
/// supported (e.g. `jj diff -r 'A|C'` in a linear chain A..C).
|
||||
///
|
||||
/// If a revision is a merge commit, this shows changes *from* the
|
||||
/// automatic merge of the contents of all of its parents *to* the contents
|
||||
/// of the revision itself.
|
||||
#[arg(
|
||||
long,
|
||||
short,
|
||||
value_name = "REVSET",
|
||||
value_name = "REVSETS",
|
||||
alias = "revision",
|
||||
add = ArgValueCandidates::new(complete::all_revisions)
|
||||
)]
|
||||
revision: Option<RevisionArg>,
|
||||
revisions: Option<Vec<RevisionArg>>,
|
||||
/// Show changes from this revision
|
||||
#[arg(
|
||||
long,
|
||||
short,
|
||||
conflicts_with = "revision",
|
||||
conflicts_with = "revisions",
|
||||
value_name = "REVSET",
|
||||
add = ArgValueCandidates::new(complete::all_revisions)
|
||||
)]
|
||||
@ -69,7 +79,7 @@ pub(crate) struct DiffArgs {
|
||||
#[arg(
|
||||
long,
|
||||
short,
|
||||
conflicts_with = "revision",
|
||||
conflicts_with = "revisions",
|
||||
value_name = "REVSET",
|
||||
add = ArgValueCandidates::new(complete::all_revisions)
|
||||
)]
|
||||
@ -95,14 +105,14 @@ pub(crate) fn cmd_diff(
|
||||
let repo = workspace_command.repo();
|
||||
let fileset_expression = workspace_command.parse_file_patterns(ui, &args.paths)?;
|
||||
let matcher = fileset_expression.to_matcher();
|
||||
let resolve_revision = |r: &Option<RevisionArg>| {
|
||||
workspace_command.resolve_single_rev(ui, r.as_ref().unwrap_or(&RevisionArg::AT))
|
||||
};
|
||||
|
||||
let from_tree;
|
||||
let to_tree;
|
||||
let mut copy_records = CopyRecords::default();
|
||||
if args.from.is_some() || args.to.is_some() {
|
||||
let resolve_revision = |r: &Option<RevisionArg>| {
|
||||
workspace_command.resolve_single_rev(ui, r.as_ref().unwrap_or(&RevisionArg::AT))
|
||||
};
|
||||
let from = resolve_revision(&args.from)?;
|
||||
let to = resolve_revision(&args.to)?;
|
||||
from_tree = from.tree()?;
|
||||
@ -111,14 +121,44 @@ pub(crate) fn cmd_diff(
|
||||
let records = get_copy_records(repo.store(), from.id(), to.id(), &matcher)?;
|
||||
copy_records.add_records(records)?;
|
||||
} else {
|
||||
let to = resolve_revision(&args.revision)?;
|
||||
let parents: Vec<_> = to.parents().try_collect()?;
|
||||
let revision_args = args
|
||||
.revisions
|
||||
.as_deref()
|
||||
.unwrap_or(std::slice::from_ref(&RevisionArg::AT));
|
||||
let revisions_evaluator = workspace_command.parse_union_revsets(ui, revision_args)?;
|
||||
let target_expression = revisions_evaluator.expression();
|
||||
let mut gaps_revset = workspace_command
|
||||
.attach_revset_evaluator(target_expression.connected().minus(target_expression))
|
||||
.evaluate_to_commit_ids()?;
|
||||
if let Some(commit_id) = gaps_revset.next() {
|
||||
return Err(user_error_with_hint(
|
||||
"Cannot diff revsets with gaps in.",
|
||||
format!(
|
||||
"Revision {} would need to be in the set.",
|
||||
short_commit_hash(&commit_id?)
|
||||
),
|
||||
));
|
||||
}
|
||||
let heads: Vec<_> = workspace_command
|
||||
.attach_revset_evaluator(target_expression.heads())
|
||||
.evaluate_to_commits()?
|
||||
.try_collect()?;
|
||||
let roots: Vec<_> = workspace_command
|
||||
.attach_revset_evaluator(target_expression.roots())
|
||||
.evaluate_to_commits()?
|
||||
.try_collect()?;
|
||||
|
||||
// Collect parents outside of revset to preserve parent order
|
||||
let parents: IndexSet<_> = roots.iter().flat_map(|c| c.parents()).try_collect()?;
|
||||
let parents = parents.into_iter().collect_vec();
|
||||
from_tree = merge_commit_trees(repo.as_ref(), &parents)?;
|
||||
to_tree = to.tree()?;
|
||||
to_tree = merge_commit_trees(repo.as_ref(), &heads)?;
|
||||
|
||||
for p in &parents {
|
||||
let records = get_copy_records(repo.store(), p.id(), to.id(), &matcher)?;
|
||||
copy_records.add_records(records)?;
|
||||
for to in &heads {
|
||||
let records = get_copy_records(repo.store(), p.id(), to.id(), &matcher)?;
|
||||
copy_records.add_records(records)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -606,7 +606,7 @@ fn modified_files_from_rev_with_jj_cmd(
|
||||
.arg("--summary")
|
||||
.arg(current_prefix_to_fileset(current));
|
||||
match rev {
|
||||
(rev, None) => cmd.arg("--revision").arg(rev),
|
||||
(rev, None) => cmd.arg("--revisions").arg(rev),
|
||||
(from, Some(to)) => cmd.arg("--from").arg(from).arg("--to").arg(to),
|
||||
};
|
||||
let output = cmd.output().map_err(user_error)?;
|
||||
|
@ -741,9 +741,11 @@ With the `--from` and/or `--to` options, shows the difference from/to the given
|
||||
|
||||
###### **Options:**
|
||||
|
||||
* `-r`, `--revision <REVSET>` — Show changes in this revision, compared to its parent(s)
|
||||
* `-r`, `--revisions <REVSETS>` — Show changes in these revisions
|
||||
|
||||
If the revision is a merge commit, this shows changes *from* the automatic merge of the contents of all of its parents *to* the contents of the revision itself.
|
||||
If there are multiple revisions, then then total diff for all of them will be shown. For example, if you have a linear chain of revisions A..D, then `jj diff -r B::D` equals `jj diff --from A --to D`. Multiple heads and/or roots are supported, but gaps in the revset are not supported (e.g. `jj diff -r 'A|C'` in a linear chain A..C).
|
||||
|
||||
If a revision is a merge commit, this shows changes *from* the automatic merge of the contents of all of its parents *to* the contents of the revision itself.
|
||||
* `-f`, `--from <REVSET>` — Show changes from this revision
|
||||
* `-t`, `--to <REVSET>` — Show changes to this revision
|
||||
* `-s`, `--summary` — For each path, show only whether it was modified, added, or deleted
|
||||
|
@ -15,8 +15,10 @@
|
||||
use indoc::indoc;
|
||||
use itertools::Itertools;
|
||||
|
||||
use crate::common::create_commit;
|
||||
use crate::common::fake_diff_editor_path;
|
||||
use crate::common::to_toml_value;
|
||||
use crate::common::CommandOutput;
|
||||
use crate::common::TestEnvironment;
|
||||
|
||||
#[test]
|
||||
@ -2843,3 +2845,90 @@ fn test_diff_binary() {
|
||||
[EOF]
|
||||
");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_diff_revisions() {
|
||||
let test_env = TestEnvironment::default();
|
||||
test_env.run_jj_in(".", ["git", "init", "repo"]).success();
|
||||
let work_dir = test_env.work_dir("repo");
|
||||
|
||||
//
|
||||
//
|
||||
//
|
||||
// E
|
||||
// |\
|
||||
// C D
|
||||
// |/
|
||||
// B
|
||||
// |
|
||||
// A
|
||||
create_commit(&work_dir, "A", &[]);
|
||||
create_commit(&work_dir, "B", &["A"]);
|
||||
create_commit(&work_dir, "C", &["B"]);
|
||||
create_commit(&work_dir, "D", &["B"]);
|
||||
create_commit(&work_dir, "E", &["C", "D"]);
|
||||
|
||||
let diff_revisions = |expression: &str| -> CommandOutput {
|
||||
work_dir.run_jj(["diff", "--name-only", "-r", expression])
|
||||
};
|
||||
// Can diff a single revision
|
||||
insta::assert_snapshot!(diff_revisions("B"), @r"
|
||||
B
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// Can diff a merge
|
||||
insta::assert_snapshot!(diff_revisions("E"), @r"
|
||||
E
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// A gap in the range is not allowed (yet at least)
|
||||
insta::assert_snapshot!(diff_revisions("A|C"), @r"
|
||||
------- stderr -------
|
||||
Error: Cannot diff revsets with gaps in.
|
||||
Hint: Revision 50c75fd767bf would need to be in the set.
|
||||
[EOF]
|
||||
[exit status: 1]
|
||||
");
|
||||
|
||||
// Can diff a linear chain
|
||||
insta::assert_snapshot!(diff_revisions("A::C"), @r"
|
||||
A
|
||||
B
|
||||
C
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// Can diff a chain with an internal merge
|
||||
insta::assert_snapshot!(diff_revisions("B::E"), @r"
|
||||
B
|
||||
C
|
||||
D
|
||||
E
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// Can diff a set with multiple roots
|
||||
insta::assert_snapshot!(diff_revisions("C|D|E"), @r"
|
||||
C
|
||||
D
|
||||
E
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// Can diff a set with multiple heads
|
||||
insta::assert_snapshot!(diff_revisions("B|C|D"), @r"
|
||||
B
|
||||
C
|
||||
D
|
||||
[EOF]
|
||||
");
|
||||
|
||||
// Can diff a set with multiple root and multiple heads
|
||||
insta::assert_snapshot!(diff_revisions("B|C"), @r"
|
||||
B
|
||||
C
|
||||
[EOF]
|
||||
");
|
||||
}
|
||||
|
@ -79,6 +79,10 @@ parent.
|
||||
<td><code>jj diff --from A --to B</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Show all the changes in A..B</td>
|
||||
<td><code>git diff A...B</code></td>
|
||||
<td><code>jj diff -r A..B</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Show description and diff of a change</td>
|
||||
<td><code>git show <revision></code></td>
|
||||
|
Loading…
x
Reference in New Issue
Block a user