Skip to content

Commit 939b093

Browse files
committed
cli: include diff of commit descriptions in inter-diff
I sometimes want to see how commit description is evolved. This patch adds a separate loop for diff of commit descriptions. This should be okay since DiffRenderer wouldn't have more than one "long" formats. Alternatively, we could add a fake TreeDiff entry so the commit description would be represented as a file in all diff formats. I have no idea which would provide better UX, so I chose the simpler one. Closes #7202
1 parent b384097 commit 939b093

File tree

5 files changed

+174
-15
lines changed

5 files changed

+174
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,9 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
8080

8181
* `jj diff --stat` now shows the change in size to binary files.
8282

83+
* `jj interdiff`, `jj evolog -p`, and `jj op log -p` now show diff of commit
84+
descriptions.
85+
8386
* `jj log` and `jj op log` output can now be anonymized with the
8487
`builtin_log_redacted` and `builtin_op_log_redacted` templates.
8588

cli/src/diff_util.rs

Lines changed: 102 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
// limitations under the License.
1414

1515
use std::borrow::Borrow;
16+
use std::borrow::Cow;
1617
use std::cmp::max;
1718
use std::io;
1819
use std::iter;
@@ -60,6 +61,7 @@ use jj_lib::files::DiffLineIterator;
6061
use jj_lib::files::DiffLineNumber;
6162
use jj_lib::matchers::Matcher;
6263
use jj_lib::merge::Merge;
64+
use jj_lib::merge::MergeBuilder;
6365
use jj_lib::merge::MergedTreeValue;
6466
use jj_lib::merged_tree::MergedTree;
6567
use jj_lib::object_id::ObjectId as _;
@@ -434,13 +436,13 @@ impl<'a> DiffRenderer<'a> {
434436
) -> Result<(), DiffRenderError> {
435437
formatter
436438
.with_label_async("diff", async |formatter| {
437-
self.show_diff_inner(ui, formatter, trees, matcher, copy_records, width)
439+
self.show_diff_trees(ui, formatter, trees, matcher, copy_records, width)
438440
.await
439441
})
440442
.await
441443
}
442444

443-
async fn show_diff_inner(
445+
async fn show_diff_trees(
444446
&self,
445447
ui: &Ui,
446448
formatter: &mut dyn Formatter,
@@ -537,6 +539,50 @@ impl<'a> DiffRenderer<'a> {
537539
Ok(())
538540
}
539541

542+
fn show_diff_commit_descriptions(
543+
&self,
544+
formatter: &mut dyn Formatter,
545+
[from_description, to_description]: [&Merge<&str>; 2],
546+
) -> Result<(), DiffRenderError> {
547+
if from_description == to_description {
548+
return Ok(());
549+
}
550+
const DUMMY_PATH: &str = "JJ-COMMIT-DESCRIPTION";
551+
for format in &self.formats {
552+
match format {
553+
// Omit diff from "short" formats. Printing dummy file path
554+
// wouldn't be useful.
555+
DiffFormat::Summary
556+
| DiffFormat::Stat(_)
557+
| DiffFormat::Types
558+
| DiffFormat::NameOnly => {}
559+
DiffFormat::Git(options) => {
560+
// Git format must be parsable, so use dummy file path.
561+
show_git_diff_texts(
562+
formatter,
563+
[DUMMY_PATH, DUMMY_PATH],
564+
[from_description, to_description],
565+
options,
566+
self.conflict_marker_style,
567+
)?;
568+
}
569+
DiffFormat::ColorWords(options) => {
570+
writeln!(formatter.labeled("header"), "Modified commit description:")?;
571+
show_color_words_diff_hunks(
572+
formatter,
573+
[from_description, to_description],
574+
options,
575+
self.conflict_marker_style,
576+
)?;
577+
}
578+
DiffFormat::Tool(_) => {
579+
// TODO: materialize commit description as file?
580+
}
581+
}
582+
}
583+
Ok(())
584+
}
585+
540586
/// Generates diff between `from_commits` and `to_commit` based off their
541587
/// parents. The `from_commits` will temporarily be rebased onto the
542588
/// `to_commit` parents to exclude unrelated changes.
@@ -549,18 +595,38 @@ impl<'a> DiffRenderer<'a> {
549595
matcher: &dyn Matcher,
550596
width: usize,
551597
) -> Result<(), DiffRenderError> {
598+
let from_description = if from_commits.is_empty() {
599+
Merge::resolved("")
600+
} else {
601+
// TODO: use common predecessor as the base description?
602+
MergeBuilder::from_iter(itertools::intersperse(
603+
from_commits.iter().map(|c| c.description()),
604+
"",
605+
))
606+
.build()
607+
.simplify()
608+
};
609+
let to_description = Merge::resolved(to_commit.description());
552610
let from_tree = rebase_to_dest_parent(self.repo, from_commits, to_commit)?;
553611
let to_tree = to_commit.tree_async().await?;
554612
let copy_records = CopyRecords::default(); // TODO
555-
self.show_diff(
556-
ui,
557-
formatter,
558-
[&from_tree, &to_tree],
559-
matcher,
560-
&copy_records,
561-
width,
562-
)
563-
.await
613+
formatter
614+
.with_label_async("diff", async |formatter| {
615+
self.show_diff_commit_descriptions(
616+
formatter,
617+
[&from_description, &to_description],
618+
)?;
619+
self.show_diff_trees(
620+
ui,
621+
formatter,
622+
[&from_tree, &to_tree],
623+
matcher,
624+
&copy_records,
625+
width,
626+
)
627+
.await
628+
})
629+
.await
564630
}
565631

566632
/// Generates diff of the given `commit` compared to its parents.
@@ -706,9 +772,9 @@ impl ColorWordsDiffOptions {
706772
}
707773
}
708774

709-
fn show_color_words_diff_hunks(
775+
fn show_color_words_diff_hunks<T: AsRef<[u8]>>(
710776
formatter: &mut dyn Formatter,
711-
[lefts, rights]: [&Merge<BString>; 2],
777+
[lefts, rights]: [&Merge<T>; 2],
712778
options: &ColorWordsDiffOptions,
713779
conflict_marker_style: ConflictMarkerStyle,
714780
) -> io::Result<()> {
@@ -1905,6 +1971,29 @@ pub async fn show_git_diff(
19051971
Ok(())
19061972
}
19071973

1974+
/// Generates diff of non-binary contents in Git format.
1975+
fn show_git_diff_texts<T: AsRef<[u8]>>(
1976+
formatter: &mut dyn Formatter,
1977+
[left_path, right_path]: [&str; 2],
1978+
contents: [&Merge<T>; 2],
1979+
options: &UnifiedDiffOptions,
1980+
conflict_marker_style: ConflictMarkerStyle,
1981+
) -> io::Result<()> {
1982+
formatter.with_label("file_header", |formatter| {
1983+
writeln!(formatter, "diff --git a/{left_path} b/{right_path}")?;
1984+
writeln!(formatter, "--- {left_path}")?;
1985+
writeln!(formatter, "+++ {right_path}")
1986+
})?;
1987+
let [left, right] = contents.map(|content| match content.as_resolved() {
1988+
Some(text) => Cow::Borrowed(BStr::new(text)),
1989+
None => Cow::Owned(materialize_merge_result_to_bytes(
1990+
content,
1991+
conflict_marker_style,
1992+
)),
1993+
});
1994+
show_unified_diff_hunks(formatter, [left.as_ref(), right.as_ref()], options)
1995+
}
1996+
19081997
#[instrument(skip_all)]
19091998
pub async fn show_diff_summary(
19101999
formatter: &mut dyn Formatter,

cli/tests/test_evolog_command.rs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,8 @@ fn test_evolog_with_or_without_diff() {
9494
○ rlvkpnrz hidden [email protected] 2001-02-03 08:05:08 b955b72e
9595
(empty) my description
9696
-- operation e0f8e58b3800 new empty commit
97+
Modified commit description:
98+
1: my description
9799
[EOF]
98100
");
99101

@@ -193,6 +195,11 @@ fn test_evolog_with_or_without_diff() {
193195
rlvkpnrz hidden [email protected] 2001-02-03 08:05:08 b955b72e
194196
(empty) my description
195197
-- operation e0f8e58b3800 new empty commit
198+
diff --git a/JJ-COMMIT-DESCRIPTION b/JJ-COMMIT-DESCRIPTION
199+
--- JJ-COMMIT-DESCRIPTION
200+
+++ JJ-COMMIT-DESCRIPTION
201+
@@ -0,0 +1,1 @@
202+
+my description
196203
[EOF]
197204
");
198205
}
@@ -410,6 +417,15 @@ fn test_evolog_squash() {
410417
○ qpvuntsm [email protected] 2001-02-03 08:05:15 5f3281c6
411418
├─┬─╮ squashed 3
412419
│ │ │ -- operation 838e6d867fda squash commits into 5ec0619af5cb4f7707a556a71a6f96af0bc294d2
420+
│ │ │ Modified commit description:
421+
│ │ │ 1 : <<<<<<< Conflict 1 of 1
422+
│ │ │ 2 : +++++++ Contents of side #1
423+
│ │ │ 3 1: squashed 2
424+
│ │ │ 4 : %%%%%%% Changes from base #1 to side #2
425+
│ │ │ 5 : +fourth
426+
│ │ │ 6 1: %%%%%%% Changes from base #2 to side #3
427+
│ │ │ 7 : +fifth
428+
│ │ │ 8 : >>>>>>> Conflict 1 of 1 ends
413429
│ │ ○ vruxwmqv hidden [email protected] 2001-02-03 08:05:15 770795d0
414430
│ │ │ fifth
415431
│ │ │ -- operation 1d38c000b52d snapshot working copy
@@ -418,6 +434,8 @@ fn test_evolog_squash() {
418434
│ │ ○ vruxwmqv hidden [email protected] 2001-02-03 08:05:14 2e0123d1
419435
│ │ (empty) fifth
420436
│ │ -- operation fc852ed87801 new empty commit
437+
│ │ Modified commit description:
438+
│ │ 1: fifth
421439
│ ○ yqosqzyt hidden [email protected] 2001-02-03 08:05:14 ea8161b6
422440
│ │ fourth
423441
│ │ -- operation 3b09d55dfa6e snapshot working copy
@@ -426,9 +444,18 @@ fn test_evolog_squash() {
426444
│ ○ yqosqzyt hidden [email protected] 2001-02-03 08:05:13 1de5fdb6
427445
│ (empty) fourth
428446
│ -- operation 9404a551035a new empty commit
447+
│ Modified commit description:
448+
│ 1: fourth
429449
○ qpvuntsm hidden [email protected] 2001-02-03 08:05:12 5ec0619a
430450
├─╮ squashed 2
431451
│ │ -- operation fa9796d12627 squash commits into 690858846504af0e42fde980fdacf9851559ebb8
452+
│ │ Modified commit description:
453+
│ │ 1 : <<<<<<< Conflict 1 of 1
454+
│ │ 2 : +++++++ Contents of side #1
455+
│ │ 3 1: squashed 1
456+
│ │ 4 1: %%%%%%% Changes from base to side #2
457+
│ │ 5 : +third
458+
│ │ 6 : >>>>>>> Conflict 1 of 1 ends
432459
│ │ Removed regular file file2:
433460
│ │ 1 : foo2
434461
│ │ Removed regular file file3:
@@ -447,12 +474,22 @@ fn test_evolog_squash() {
447474
│ ○ zsuskuln hidden [email protected] 2001-02-03 08:05:11 3a2a4253
448475
│ │ (empty) third
449476
│ │ -- operation 4611a6121e8a describe commit ebec10f449ad7ab92c7293efab5e3db2d8e9fea1
477+
│ │ Modified commit description:
478+
│ │ 1: third
450479
│ ○ zsuskuln hidden [email protected] 2001-02-03 08:05:10 ebec10f4
451480
│ (empty) (no description set)
452481
│ -- operation 65c81703100d squash commits into 5878cbe03cdf599c9353e5a1a52a01f4c5e0e0fa
453482
○ qpvuntsm hidden [email protected] 2001-02-03 08:05:10 69085884
454483
├─╮ squashed 1
455484
│ │ -- operation 65c81703100d squash commits into 5878cbe03cdf599c9353e5a1a52a01f4c5e0e0fa
485+
│ │ Modified commit description:
486+
│ │ 1 : <<<<<<< Conflict 1 of 1
487+
│ │ 2 : %%%%%%% Changes from base to side #1
488+
│ │ 3 : +first
489+
│ │ 4 : +++++++ Contents of side #2
490+
│ │ 5 : second
491+
│ │ 6 : >>>>>>> Conflict 1 of 1 ends
492+
│ │ 1: squashed 1
456493
│ ○ kkmpptxz hidden [email protected] 2001-02-03 08:05:10 a3759c9d
457494
│ │ second
458495
│ │ -- operation a7b202f56742 snapshot working copy
@@ -462,6 +499,8 @@ fn test_evolog_squash() {
462499
│ ○ kkmpptxz hidden [email protected] 2001-02-03 08:05:09 a5b2f625
463500
│ (empty) second
464501
│ -- operation 26f649a0cdfa new empty commit
502+
│ Modified commit description:
503+
│ 1: second
465504
○ qpvuntsm hidden [email protected] 2001-02-03 08:05:09 5878cbe0
466505
│ first
467506
│ -- operation af15122a5868 snapshot working copy
@@ -470,6 +509,8 @@ fn test_evolog_squash() {
470509
○ qpvuntsm hidden [email protected] 2001-02-03 08:05:08 68a50538
471510
│ (empty) first
472511
│ -- operation 75545f7ff2df describe commit e8849ae12c709f2321908879bc724fdb2ab8a781
512+
│ Modified commit description:
513+
│ 1: first
473514
○ qpvuntsm hidden [email protected] 2001-02-03 08:05:07 e8849ae1
474515
(empty) (no description set)
475516
-- operation 8f47435a3990 add workspace 'default'

cli/tests/test_interdiff_command.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,15 +21,15 @@ fn test_interdiff_basic() {
2121
let work_dir = test_env.work_dir("repo");
2222

2323
work_dir.write_file("file1", "foo\n");
24-
work_dir.run_jj(["new"]).success();
24+
work_dir.run_jj(["new", "-madd file2 left"]).success();
2525
work_dir.write_file("file2", "foo\n");
2626
work_dir
2727
.run_jj(["bookmark", "create", "-r@", "left"])
2828
.success();
2929

3030
work_dir.run_jj(["new", "root()"]).success();
3131
work_dir.write_file("file3", "foo\n");
32-
work_dir.run_jj(["new"]).success();
32+
work_dir.run_jj(["new", "-madd file2 right"]).success();
3333
work_dir.write_file("file2", "foo\nbar\n");
3434
work_dir
3535
.run_jj(["bookmark", "create", "-r@", "right"])
@@ -39,6 +39,8 @@ fn test_interdiff_basic() {
3939
// implicit --to
4040
let output = work_dir.run_jj(["interdiff", "--from", "left"]);
4141
insta::assert_snapshot!(output, @r"
42+
Modified commit description:
43+
1 1: add file2 leftright
4244
Modified regular file file2:
4345
1 1: foo
4446
2: bar
@@ -49,6 +51,8 @@ fn test_interdiff_basic() {
4951
work_dir.run_jj(["new", "@-"]).success();
5052
let output = work_dir.run_jj(["interdiff", "--from", "left", "--to", "right"]);
5153
insta::assert_snapshot!(output, @r"
54+
Modified commit description:
55+
1 1: add file2 leftright
5256
Modified regular file file2:
5357
1 1: foo
5458
2: bar
@@ -65,6 +69,12 @@ fn test_interdiff_basic() {
6569

6670
let output = work_dir.run_jj(["interdiff", "--from", "left", "--to", "right", "--git"]);
6771
insta::assert_snapshot!(output, @r"
72+
diff --git a/JJ-COMMIT-DESCRIPTION b/JJ-COMMIT-DESCRIPTION
73+
--- JJ-COMMIT-DESCRIPTION
74+
+++ JJ-COMMIT-DESCRIPTION
75+
@@ -1,1 +1,1 @@
76+
-add file2 left
77+
+add file2 right
6878
diff --git a/file2 b/file2
6979
index 257cc5642c..3bd1f0e297 100644
7080
--- a/file2

cli/tests/test_operations.rs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1882,6 +1882,11 @@ fn test_op_diff_divergent_change() {
18821882
Changed commits:
18831883
○ + rlvkpnrz?? c5cad9ab 2b
18841884
- rlvkpnrz hidden 4f7a567a (empty) (no description set)
1885+
diff --git a/JJ-COMMIT-DESCRIPTION b/JJ-COMMIT-DESCRIPTION
1886+
--- JJ-COMMIT-DESCRIPTION
1887+
+++ JJ-COMMIT-DESCRIPTION
1888+
@@ -0,0 +1,1 @@
1889+
+2b
18851890
diff --git a/file b/file
18861891
index d00491fd7e..5e0f51b37b 100644
18871892
--- a/file
@@ -1891,6 +1896,11 @@ fn test_op_diff_divergent_change() {
18911896
+2b
18921897
○ + rlvkpnrz?? f189cafa 2a
18931898
- rlvkpnrz hidden 4f7a567a (empty) (no description set)
1899+
diff --git a/JJ-COMMIT-DESCRIPTION b/JJ-COMMIT-DESCRIPTION
1900+
--- JJ-COMMIT-DESCRIPTION
1901+
+++ JJ-COMMIT-DESCRIPTION
1902+
@@ -0,0 +1,1 @@
1903+
+2a
18941904
diff --git a/file b/file
18951905
index d00491fd7e..13a46f22fa 100644
18961906
--- a/file
@@ -1923,6 +1933,12 @@ fn test_op_diff_divergent_change() {
19231933
○ + rlvkpnrz 17d68d92 2ab
19241934
- rlvkpnrz hidden c5cad9ab 2b
19251935
- rlvkpnrz hidden f189cafa 2a
1936+
diff --git a/JJ-COMMIT-DESCRIPTION b/JJ-COMMIT-DESCRIPTION
1937+
--- JJ-COMMIT-DESCRIPTION
1938+
+++ JJ-COMMIT-DESCRIPTION
1939+
@@ -1,1 +1,1 @@
1940+
-2b
1941+
+2ab
19261942
diff --git a/file b/file
19271943
index 5e0f51b37b..60327514e0 100644
19281944
--- a/file

0 commit comments

Comments
 (0)