@@ -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 } ;
1213use hyper:: header:: CACHE_CONTROL ;
1314use 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- } ;
2018use pulldown_cmark_escape:: FmtWriter ;
2119use regex:: Regex ;
2220use 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} before | {ADDED_BLOCK_SIGN} 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`
683742struct 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
699770fn 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