Skip to content

Commit

Permalink
copy-tracking: add support for copies tracking to diff --stat
Browse files Browse the repository at this point in the history
  • Loading branch information
fowles committed Aug 9, 2024
1 parent f298f37 commit 92434cd
Show file tree
Hide file tree
Showing 6 changed files with 84 additions and 14 deletions.
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
### New features

* `jj diff` the following subcommands now include information about copies and
moves if supported by the backend: `jj diff --summary`
moves if supported by the backend: `jj diff --summary`, `jj diff --stat`

### Fixed bugs

Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ toml_edit = { workspace = true }
tracing = { workspace = true }
tracing-chrome = { workspace = true }
tracing-subscriber = { workspace = true }
unicode-segmentation = "1.11.0"
unicode-width = { workspace = true }

[target.'cfg(unix)'.dependencies]
Expand Down
31 changes: 22 additions & 9 deletions cli/src/diff_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
// limitations under the License.

use std::cmp::max;
use std::collections::{HashMap, VecDeque};
use std::collections::{HashMap, HashSet, VecDeque};
use std::ops::Range;
use std::path::{Path, PathBuf};
use std::{io, mem};
Expand Down Expand Up @@ -284,8 +284,7 @@ impl<'a> DiffRenderer<'a> {
show_diff_summary(formatter, tree_diff, path_converter)?;
}
DiffFormat::Stat => {
let no_copy_tracking = Default::default();
let tree_diff = from_tree.diff_stream(to_tree, matcher, &no_copy_tracking);
let tree_diff = from_tree.diff_stream(to_tree, matcher, &copy_records);
show_diff_stat(formatter, store, tree_diff, path_converter, width)?;
}
DiffFormat::Types => {
Expand Down Expand Up @@ -1226,6 +1225,7 @@ struct DiffStat {
path: String,
added: usize,
removed: usize,
is_deletion: bool,
}

fn get_diff_stat(
Expand Down Expand Up @@ -1253,6 +1253,7 @@ fn get_diff_stat(
path,
added,
removed,
is_deletion: right_content.contents.is_empty(),
}
}

Expand All @@ -1264,21 +1265,28 @@ pub fn show_diff_stat(
display_width: usize,
) -> Result<(), DiffRenderError> {
let mut stats: Vec<DiffStat> = vec![];
let mut unresolved_renames = HashSet::<String>::new();
let mut max_path_width = 0;
let mut max_diffs = 0;

let mut diff_stream = materialized_diff_stream(store, tree_diff);
async {
while let Some(MaterializedTreeDiffEntry {
source: _,
target: repo_path,
source: left_path,
target: right_path,
value: diff,
}) = diff_stream.next().await
{
let (left, right) = diff?;
let path = path_converter.format_file_path(&repo_path);
let left_content = diff_content(&repo_path, left)?;
let right_content = diff_content(&repo_path, right)?;
let left_content = diff_content(&left_path, left)?;
let right_content = diff_content(&right_path, right)?;

let left_ui_path = path_converter.format_file_path(&left_path);
let right_ui_path = path_converter.format_file_path(&right_path);
if left_ui_path != right_ui_path {
unresolved_renames.insert(left_ui_path.clone());
}
let path = text_util::render_copied_path(&left_ui_path, &right_ui_path);
max_path_width = max(max_path_width, path.width());
let stat = get_diff_stat(path, &left_content, &right_content);
max_diffs = max(max_diffs, stat.added + stat.removed);
Expand All @@ -1303,10 +1311,15 @@ pub fn show_diff_stat(

let mut total_added = 0;
let mut total_removed = 0;
let total_files = stats.len();
let mut total_files = 0;
for stat in &stats {
if stat.is_deletion && unresolved_renames.contains(&stat.path) {
continue;
}

total_added += stat.added;
total_removed += stat.removed;
total_files += 1;
let bar_added = (stat.added as f64 * factor).ceil() as usize;
let bar_removed = (stat.removed as f64 * factor).ceil() as usize;
// replace start of path with ellipsis if the path is too long
Expand Down
56 changes: 56 additions & 0 deletions cli/src/text_util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@
use std::borrow::Cow;
use std::{cmp, io};

use itertools::Itertools;

use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthChar as _;

use crate::formatter::{FormatRecorder, Formatter};
Expand All @@ -35,6 +38,33 @@ pub fn split_email(email: &str) -> (&str, Option<&str>) {
}
}

pub fn render_copied_path(source: &str, target: &str) -> String {
if source == target {
return target.into();
}
let prefix = UnicodeSegmentation::split_word_bound_indices(source)
.zip(UnicodeSegmentation::split_word_bound_indices(target))
.take_while_inclusive(|((_, s), (_, t))| s == t)
.last()
.map(|((s, _), (t, _))| (s, t))
.unwrap_or((0, 0));
let suffix = UnicodeSegmentation::split_word_bound_indices(source)
.rev()
.zip(UnicodeSegmentation::split_word_bound_indices(target).rev())
.take_while(|((_, s), (_, t))| s == t)
.last()
.map(|((s, _), (t, _))| (s, t))
.unwrap_or((source.len(), target.len()));

format!(
"{}{{{} => {}}}{}",
&source[0..prefix.0],
&source[prefix.0..suffix.0.max(prefix.0)],
&target[prefix.1..suffix.1.max(prefix.1)],
&source[suffix.0..]
)
}

/// Shortens `text` to `max_width` by removing leading characters. `ellipsis` is
/// added if the `text` gets truncated.
///
Expand Down Expand Up @@ -629,4 +659,30 @@ mod tests {
"foo\n",
);
}

#[test]
fn test_render_copied_path() {
assert_eq!(
render_copied_path("one/two/three", "one/two/three"),
"one/two/three"
);
assert_eq!(
render_copied_path("two/three", "four/three"),
"{two => four}/three"
);
assert_eq!(
render_copied_path("one/two/three", "one/four/three"),
"one/{two => four}/three"
);
assert_eq!(
render_copied_path("one/two/three", "one/three"),
"one/{two => }/three"
);
assert_eq!(
render_copied_path("one/two", "one/four"),
"one/{two => four}"
);
assert_eq!(render_copied_path("two", "four"), "{two => four}");
assert_eq!(render_copied_path("file1", "file2"), "{file1 => file2}");
}
}
7 changes: 3 additions & 4 deletions cli/tests/test_diff_command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,10 +189,9 @@ fn test_diff_basic() {

let stdout = test_env.jj_cmd_success(&repo_path, &["diff", "--stat"]);
insta::assert_snapshot!(stdout, @r###"
file1 | 1 -
file2 | 3 ++-
file3 | 1 +
3 files changed, 3 insertions(+), 2 deletions(-)
file2 | 3 ++-
{file1 => file3} | 0
2 files changed, 2 insertions(+), 1 deletion(-)
"###);

// Filter by glob pattern
Expand Down

0 comments on commit 92434cd

Please sign in to comment.