Skip to content

Commit 6020747

Browse files
authored
Merge pull request #2398 from Urgau/gh-range-diff-hide-more
Filter-out context-only hunks in our range-diff
2 parents 53ab438 + f4603d3 commit 6020747

3 files changed

Lines changed: 129 additions & 67 deletions

File tree

Cargo.lock

Lines changed: 12 additions & 21 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ clap = { version = "4", features = ["derive", "string"] }
4747
hmac = "0.12.1"
4848
subtle = "2.6.1"
4949
sha2 = "0.10.9"
50-
imara-diff = "0.2.0"
50+
gix-imara-diff = { version = "0.2.1", features = ["unified_diff"] }
5151
pulldown-cmark-escape = "0.11.0"
5252
axum-extra = { version = "0.10.1", default-features = false }
5353
unicode-segmentation = "1.12.0"

src/gh_range_diff.rs

Lines changed: 116 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,12 @@ use axum::{
99
http::HeaderValue,
1010
response::IntoResponse,
1111
};
12+
use gix_imara_diff::{Algorithm, Diff, Hunk, InternedInput, Interner, Token, UnifiedDiffPrinter};
1213
use hyper::header::CACHE_CONTROL;
1314
use hyper::{
1415
HeaderMap, StatusCode,
1516
header::{CONTENT_SECURITY_POLICY, CONTENT_TYPE},
1617
};
17-
use imara_diff::{
18-
Algorithm, Diff, InternedInput, Interner, Token, UnifiedDiffConfig, UnifiedDiffPrinter,
19-
};
2018
use pulldown_cmark_escape::FmtWriter;
2119
use regex::Regex;
2220
use unicode_segmentation::UnicodeSegmentation;
@@ -200,7 +198,7 @@ fn process_old_new(
200198
(newbase, newhead, mut new): (&str, &str, GithubCompare),
201199
) -> axum::response::Result<(StatusCode, HeaderMap, String), AppError> {
202200
// Configure unified diff
203-
let config = UnifiedDiffConfig::default();
201+
let config = CustomUnifiedDiffConfig { context_len: 3 };
204202

205203
// Sort by filename, so it's consistent with GitHub UI
206204
old.files
@@ -291,12 +289,6 @@ fn process_old_new(
291289
.spacer {{
292290
margin-bottom: 1rem;
293291
}}
294-
.hide {{
295-
display: none;
296-
}}
297-
#show-context-only-changes-cb:checked ~ .hide {{
298-
display: block;
299-
}}
300292
@media (prefers-color-scheme: dark) {{
301293
body {{
302294
background: #0C0C0C;
@@ -350,8 +342,6 @@ fn process_old_new(
350342
<body>
351343
<h3>range-diff of {a_oldbase}..{a_oldhead} {a_newbase}..{a_newhead} in {owner}/{repo}</h3>
352344
<span>Legend: {REMOVED_BLOCK_SIGN}&nbsp;before | {ADDED_BLOCK_SIGN}&nbsp;after</span>
353-
<input type="checkbox" id="show-context-only-changes-cb">
354-
<label for="show-context-only-changes-cb"> Show context-only changes</label>
355345
<div class="spacer"></div>
356346
"#
357347
)?;
@@ -373,47 +363,36 @@ fn process_old_new(
373363
// Run postprocessing to improve hunk boundaries
374364
diff.postprocess_lines(&input);
375365

376-
// Determine if there are any differences
377-
let has_hunks = diff.hunks().next().is_some();
378-
379-
if has_hunks {
380-
let has_content_changes = 'context: {
381-
for mut hunk in diff.hunks() {
382-
let contains_diff_marker = |idx: u32, source: &[Token]| {
383-
let line = &input.interner[source[idx as usize]];
384-
line.starts_with('+') || line.starts_with('-')
385-
};
386-
387-
if hunk.before.any(|i| contains_diff_marker(i, &input.before))
388-
|| hunk.after.any(|i| contains_diff_marker(i, &input.after))
389-
{
390-
break 'context true;
391-
}
392-
}
393-
false
394-
};
395-
366+
// Collect and filter-out hunks don't contain any diff marker (-, +)
367+
// as those are context-only changes, which are not interesting in
368+
// a range-diff.
369+
//
370+
// See <https://github.com/rust-lang/triagebot/issues/2394>
371+
let hunks = diff
372+
.hunks()
373+
.filter(|hunk| contains_diff_marker(&input, hunk.clone()))
374+
.collect::<Vec<_>>();
375+
376+
// Show the changes if there are any hunks to be shown
377+
if !hunks.is_empty() {
396378
let printer = HtmlDiffPrinter(&input.interner, filename);
397-
let diff = diff.unified_diff(&printer, config.clone(), &input);
379+
let unified_diff = CustomUnifiedDiff {
380+
printer: &printer,
381+
hunks: &hunks,
382+
config: config.clone(),
383+
before: &input.before,
384+
after: &input.after,
385+
};
398386

399387
let before_href =
400388
format_args!("https://github.com/{owner}/{repo}/blob/{oldhead}/{filename}");
401389
let after_href =
402390
format_args!("https://github.com/{owner}/{repo}/blob/{newhead}/{filename}");
403391

404-
if has_content_changes {
405-
write!(html, r#"<details open=""><summary>{filename}"#)?;
406-
} else {
407-
/* Context only changes, hide by default */
408-
write!(
409-
html,
410-
r#"<details open="" class="hide" data-context-only-changes=""><summary>{filename} <i>(context-only changes)</i>"#
411-
)?;
412-
}
413-
392+
write!(html, r#"<details open=""><summary>{filename}"#)?;
414393
write!(
415394
html,
416-
r#" <a href="{before_href}">before</a> <a href="{after_href}">after</a></summary><pre class="diff-content">{diff}</pre>"#
395+
r#" <a href="{before_href}">before</a> <a href="{after_href}">after</a></summary><pre class="diff-content">{unified_diff}</pre>"#
417396
)?;
418397
writeln!(html, "</details>")?;
419398

@@ -679,10 +658,90 @@ impl UnifiedDiffPrinter for HtmlDiffPrinter<'_> {
679658
}
680659
}
681660

661+
/// Custom imara-diff UnifiedDiff
662+
struct CustomUnifiedDiff<'a, P: UnifiedDiffPrinter> {
663+
printer: &'a P,
664+
hunks: &'a [Hunk],
665+
config: CustomUnifiedDiffConfig,
666+
before: &'a [Token],
667+
after: &'a [Token],
668+
}
669+
670+
#[derive(Debug, Clone, PartialEq, Eq)]
671+
struct CustomUnifiedDiffConfig {
672+
context_len: u32,
673+
}
674+
675+
// Customized version of <https://github.com/GitoxideLabs/gitoxide/blob/8af2691270a72c711bbec8100ce07273de29f52a/gix-imara-diff/src/unified_diff.rs#L218>
676+
impl<P: UnifiedDiffPrinter> fmt::Display for CustomUnifiedDiff<'_, P> {
677+
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
678+
let first_hunk = self.hunks.first().cloned().unwrap_or_default();
679+
let context_len = self.config.context_len.min(1024 * 1024);
680+
let mut pos = first_hunk.before.start.saturating_sub(context_len);
681+
let mut before_context_start = pos;
682+
let mut after_context_start = first_hunk.after.start.saturating_sub(context_len);
683+
let mut before_context_len = 0;
684+
let mut after_context_len = 0;
685+
let mut buffer = String::new();
686+
for hunk in self.hunks {
687+
if hunk.before.start - pos > 2 * context_len {
688+
if !buffer.is_empty() {
689+
let end = (pos + context_len).min(self.before.len() as u32);
690+
self.printer.display_header(
691+
&mut *f,
692+
before_context_start,
693+
after_context_start,
694+
before_context_len + end - pos,
695+
after_context_len + end - pos,
696+
)?;
697+
write!(f, "{buffer}")?;
698+
for &token in &self.before[pos as usize..end as usize] {
699+
self.printer.display_context_token(&mut *f, token)?;
700+
}
701+
buffer.clear();
702+
}
703+
pos = hunk.before.start - context_len;
704+
before_context_start = pos;
705+
after_context_start = hunk.after.start - context_len;
706+
before_context_len = 0;
707+
after_context_len = 0;
708+
}
709+
for &token in &self.before[pos as usize..hunk.before.start as usize] {
710+
self.printer.display_context_token(&mut buffer, token)?;
711+
}
712+
let context_len = hunk.before.start - pos;
713+
before_context_len += hunk.before.len() as u32 + context_len;
714+
after_context_len += hunk.after.len() as u32 + context_len;
715+
self.printer.display_hunk(
716+
&mut buffer,
717+
&self.before[hunk.before.start as usize..hunk.before.end as usize],
718+
&self.after[hunk.after.start as usize..hunk.after.end as usize],
719+
)?;
720+
pos = hunk.before.end;
721+
}
722+
if !buffer.is_empty() {
723+
let end = (pos + context_len).min(self.before.len() as u32);
724+
self.printer.display_header(
725+
&mut *f,
726+
before_context_start,
727+
after_context_start,
728+
before_context_len + end - pos,
729+
after_context_len + end - pos,
730+
)?;
731+
write!(f, "{buffer}")?;
732+
for &token in &self.before[pos as usize..end as usize] {
733+
self.printer.display_context_token(&mut *f, token)?;
734+
}
735+
buffer.clear();
736+
}
737+
Ok(())
738+
}
739+
}
740+
682741
// Simple abstraction over `unicode_segmentation::split_word_bounds` for `imara_diff::TokenSource`
683742
struct SplitWordBoundaries<'a>(&'a str);
684743

685-
impl<'a> imara_diff::TokenSource for SplitWordBoundaries<'a> {
744+
impl<'a> gix_imara_diff::TokenSource for SplitWordBoundaries<'a> {
686745
type Token = &'a str;
687746
type Tokenizer = unicode_segmentation::UWordBounds<'a>;
688747

@@ -696,6 +755,18 @@ impl<'a> imara_diff::TokenSource for SplitWordBoundaries<'a> {
696755
}
697756
}
698757

758+
// Determine if a hunk contains any diff marker (+, -) in the underline inputs
759+
fn contains_diff_marker(input: &InternedInput<&str>, mut hunk: Hunk) -> bool {
760+
let contains_diff_marker = |idx: u32, source: &[Token]| {
761+
let line = &input.interner[source[idx as usize]];
762+
line.starts_with('+') || line.starts_with('-')
763+
};
764+
765+
hunk.before.any(|i| contains_diff_marker(i, &input.before))
766+
|| hunk.after.any(|i| contains_diff_marker(i, &input.after))
767+
}
768+
769+
// function to create a <a> to a GitHub commit
699770
fn a_github_commit(owner: &str, repo: &str, ref_: &str) -> String {
700771
format!(
701772
r#"<a href="https://github.com/{owner}/{repo}/commit/{ref_}" class="commit">{}</a>"#,

0 commit comments

Comments
 (0)