Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package riff for openSUSE:Factory checked in 
at 2026-08-17 16:59:28
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/riff (Old)
 and      /work/SRC/openSUSE:Factory/.riff.new.1258 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "riff"

Mon Aug 17 16:59:28 2026 rev:29 rq:1371510 version:3.6.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/riff/riff.changes        2026-01-19 
18:42:09.920138328 +0100
+++ /work/SRC/openSUSE:Factory/.riff.new.1258/riff.changes      2026-08-17 
17:02:51.284219112 +0200
@@ -1,0 +2,7 @@
+Mon Aug 17 06:22:45 UTC 2026 - Michael Vetter <[email protected]>
+
+- Update to 3.6.2:
+  * The file header hyperlink pointed to the first line of the file.
+    Now it points to the first change in the file.
+
+-------------------------------------------------------------------

Old:
----
  riff-3.6.1.tar.zst

New:
----
  riff-3.6.2.tar.zst

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ riff.spec ++++++
--- /var/tmp/diff_new_pack.vXjVdy/_old  2026-08-17 17:02:52.404258653 +0200
+++ /var/tmp/diff_new_pack.vXjVdy/_new  2026-08-17 17:02:52.407258759 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           riff
-Version:        3.6.1
+Version:        3.6.2
 Release:        0
 Summary:        A diff filter highlighting which line parts have changed
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.vXjVdy/_old  2026-08-17 17:02:52.465260804 +0200
+++ /var/tmp/diff_new_pack.vXjVdy/_new  2026-08-17 17:02:52.469260946 +0200
@@ -3,7 +3,7 @@
     <param name="url">https://github.com/walles/riff.git</param>
     <param name="versionformat">@PARENT_TAG@</param>
     <param name="scm">git</param>
-    <param name="revision">3.6.1</param>
+    <param name="revision">3.6.2</param>
     <param name="match-tag">*</param>
     <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param>
     <param name="versionrewrite-replacement">\1</param>

++++++ riff-3.6.1.tar.zst -> riff-3.6.2.tar.zst ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/Cargo.lock new/riff-3.6.2/Cargo.lock
--- old/riff-3.6.1/Cargo.lock   2025-11-17 20:58:21.000000000 +0100
+++ new/riff-3.6.2/Cargo.lock   2026-08-15 23:27:15.000000000 +0200
@@ -113,9 +113,9 @@
 
 [[package]]
 name = "bitflags"
-version = "1.3.2"
+version = "2.10.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
+checksum = "812e12b5285cc515a9c72a5c1d3b6d46a19dac5acfef5265968c166106e31dd3"
 
 [[package]]
 name = "bytecount"
@@ -210,22 +210,19 @@
 
 [[package]]
 name = "errno"
-version = "0.3.5"
+version = "0.3.14"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "ac3e13f66a2f95e32a39eaa81f6b95d42878ca0e1db0c7543723dfe12557e860"
+checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
 dependencies = [
  "libc",
- "windows-sys 0.48.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
 name = "fastrand"
-version = "1.9.0"
+version = "2.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "e51093e27b0797c359783294ca4f0a911c270184cb10f85783b118614a1501be"
-dependencies = [
- "instant",
-]
+checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be"
 
 [[package]]
 name = "form_urlencoded"
@@ -237,6 +234,18 @@
 ]
 
 [[package]]
+name = "getrandom"
+version = "0.3.4"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd"
+dependencies = [
+ "cfg-if",
+ "libc",
+ "r-efi",
+ "wasip2",
+]
+
+[[package]]
 name = "gimli"
 version = "0.23.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -387,25 +396,6 @@
 ]
 
 [[package]]
-name = "instant"
-version = "0.1.12"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c"
-dependencies = [
- "cfg-if",
-]
-
-[[package]]
-name = "io-lifetimes"
-version = "1.0.6"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "cfa919a82ea574332e2de6e74b4c36e74d41982b335080fa59d4ef31be20fdf3"
-dependencies = [
- "libc",
- "windows-sys 0.45.0",
-]
-
-[[package]]
 name = "is_terminal_polyfill"
 version = "1.70.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -422,15 +412,15 @@
 
 [[package]]
 name = "libc"
-version = "0.2.140"
+version = "0.2.180"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "99227334921fae1a979cf0bfdfcc6b3e5ce376ef57e16fb6fb3ea2ed6095f80c"
+checksum = "bcc35a38544a891a5f7c865aca548a982ccb3b8650a5b06d0fd33a10283c56fc"
 
 [[package]]
 name = "linux-raw-sys"
-version = "0.1.4"
+version = "0.11.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
+checksum = "df1d3c3b53da64cf5760482273a98e575c651a67eec7f77df96b5b642de8f039"
 
 [[package]]
 name = "litemap"
@@ -543,13 +533,10 @@
 ]
 
 [[package]]
-name = "redox_syscall"
-version = "0.2.10"
+name = "r-efi"
+version = "5.3.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "8383f39639269cde97d255a32bdb68c047337295414940c68bdd30c2e13203ff"
-dependencies = [
- "bitflags",
-]
+checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
 
 [[package]]
 name = "regex"
@@ -570,7 +557,7 @@
 
 [[package]]
 name = "riffdiff"
-version = "3.6.1"
+version = "3.6.2"
 dependencies = [
  "backtrace",
  "base64",
@@ -598,16 +585,15 @@
 
 [[package]]
 name = "rustix"
-version = "0.36.16"
+version = "1.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "6da3636faa25820d8648e0e31c5d519bbb01f72fdf57131f0f5f7da5fed36eab"
+checksum = "146c9e247ccc180c1f61615433868c99f3de3ae256a30a43b49f67c2d9171f34"
 dependencies = [
  "bitflags",
  "errno",
- "io-lifetimes",
  "libc",
  "linux-raw-sys",
- "windows-sys 0.45.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -695,15 +681,15 @@
 
 [[package]]
 name = "tempfile"
-version = "3.4.0"
+version = "3.24.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "af18f7ae1acd354b992402e9ec5864359d693cd8a79dcbef59f76891701c1e95"
+checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
 dependencies = [
- "cfg-if",
  "fastrand",
- "redox_syscall",
+ "getrandom",
+ "once_cell",
  "rustix",
- "windows-sys 0.42.0",
+ "windows-sys 0.59.0",
 ]
 
 [[package]]
@@ -761,6 +747,15 @@
 checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
 
 [[package]]
+name = "wasip2"
+version = "1.0.2+wasi-0.2.9"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5"
+dependencies = [
+ "wit-bindgen",
+]
+
+[[package]]
 name = "winapi"
 version = "0.3.9"
 source = "registry+https://github.com/rust-lang/crates.io-index";
@@ -784,30 +779,6 @@
 
 [[package]]
 name = "windows-sys"
-version = "0.42.0"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
-dependencies = [
- "windows_aarch64_gnullvm 0.42.1",
- "windows_aarch64_msvc 0.42.1",
- "windows_i686_gnu 0.42.1",
- "windows_i686_msvc 0.42.1",
- "windows_x86_64_gnu 0.42.1",
- "windows_x86_64_gnullvm 0.42.1",
- "windows_x86_64_msvc 0.42.1",
-]
-
-[[package]]
-name = "windows-sys"
-version = "0.45.0"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0"
-dependencies = [
- "windows-targets 0.42.1",
-]
-
-[[package]]
-name = "windows-sys"
 version = "0.48.0"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
@@ -826,21 +797,6 @@
 
 [[package]]
 name = "windows-targets"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "8e2522491fbfcd58cc84d47aeb2958948c4b8982e9a2d8a2a35bbaed431390e7"
-dependencies = [
- "windows_aarch64_gnullvm 0.42.1",
- "windows_aarch64_msvc 0.42.1",
- "windows_i686_gnu 0.42.1",
- "windows_i686_msvc 0.42.1",
- "windows_x86_64_gnu 0.42.1",
- "windows_x86_64_gnullvm 0.42.1",
- "windows_x86_64_msvc 0.42.1",
-]
-
-[[package]]
-name = "windows-targets"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
@@ -872,12 +828,6 @@
 
 [[package]]
 name = "windows_aarch64_gnullvm"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
-
-[[package]]
-name = "windows_aarch64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
@@ -890,12 +840,6 @@
 
 [[package]]
 name = "windows_aarch64_msvc"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
-
-[[package]]
-name = "windows_aarch64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
@@ -908,12 +852,6 @@
 
 [[package]]
 name = "windows_i686_gnu"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
-
-[[package]]
-name = "windows_i686_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
@@ -932,12 +870,6 @@
 
 [[package]]
 name = "windows_i686_msvc"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
-
-[[package]]
-name = "windows_i686_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
@@ -950,12 +882,6 @@
 
 [[package]]
 name = "windows_x86_64_gnu"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
-
-[[package]]
-name = "windows_x86_64_gnu"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
@@ -968,12 +894,6 @@
 
 [[package]]
 name = "windows_x86_64_gnullvm"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
-
-[[package]]
-name = "windows_x86_64_gnullvm"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
@@ -986,12 +906,6 @@
 
 [[package]]
 name = "windows_x86_64_msvc"
-version = "0.42.1"
-source = "registry+https://github.com/rust-lang/crates.io-index";
-checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"
-
-[[package]]
-name = "windows_x86_64_msvc"
 version = "0.48.5"
 source = "registry+https://github.com/rust-lang/crates.io-index";
 checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
@@ -1003,6 +917,12 @@
 checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
 
 [[package]]
+name = "wit-bindgen"
+version = "0.51.0"
+source = "registry+https://github.com/rust-lang/crates.io-index";
+checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5"
+
+[[package]]
 name = "writeable"
 version = "0.6.1"
 source = "registry+https://github.com/rust-lang/crates.io-index";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/Cargo.toml new/riff-3.6.2/Cargo.toml
--- old/riff-3.6.1/Cargo.toml   2025-11-17 20:58:21.000000000 +0100
+++ new/riff-3.6.2/Cargo.toml   2026-08-15 23:27:15.000000000 +0200
@@ -2,7 +2,7 @@
 
 [package]
 name = "riffdiff"                                             # Actually 
"riff", but that was already taken on crates.io
-version = "3.6.1"
+version = "3.6.2"
 authors = ["Johan Walles <[email protected]>"]
 edition = "2018"
 repository = "https://github.com/walles/riff/";
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/README.md new/riff-3.6.2/README.md
--- old/riff-3.6.1/README.md    2025-11-17 20:58:21.000000000 +0100
+++ new/riff-3.6.2/README.md    2026-08-15 23:27:15.000000000 +0200
@@ -55,10 +55,10 @@
 brew install riff
 ```
 
-## With [Archlinux User Repository 
(AUR)](https://aur.archlinux.org/packages/riffdiff)
+## With [Archlinux User Repository 
(AUR)](https://aur.archlinux.org/packages/riffdiff-bin)
 
 ```
-paru -S riffdiff
+paru -S riffdiff-bin
 ```
 
 ## From [the Rust Crate](https://crates.io/crates/riffdiff)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/release.sh new/riff-3.6.2/release.sh
--- old/riff-3.6.1/release.sh   2025-11-17 20:58:21.000000000 +0100
+++ new/riff-3.6.2/release.sh   2026-08-15 23:27:15.000000000 +0200
@@ -15,7 +15,7 @@
 set -eu -o pipefail
 
 # List available SDKs using "xcodebuild -showsdks"
-CROSSBUILD_MACOS_SDK="macosx26.1"
+CROSSBUILD_MACOS_SDK="macosx26.2"
 
 # Git hooks can use this variable to avoid duplicating the CI work we do in 
here
 # anyway.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/src/file_highlighter.rs 
new/riff-3.6.2/src/file_highlighter.rs
--- old/riff-3.6.1/src/file_highlighter.rs      2025-11-17 20:58:21.000000000 
+0100
+++ new/riff-3.6.2/src/file_highlighter.rs      2026-08-15 23:27:15.000000000 
+0200
@@ -10,6 +10,7 @@
     render, Style, StyledToken, LINE_STYLE_NEW_FILENAME, 
LINE_STYLE_OLD_FILENAME,
 };
 
+use crate::hunk_header::HunkHeader;
 use crate::hunk_highlighter::HunkLinesHighlighter;
 use crate::refiner::Formatter;
 
@@ -28,6 +29,13 @@
 
     /// URL to the file we're currently highlighting, if any
     url: Option<url::Url>,
+
+    /// Set once the `--- ` / `+++ ` header has been emitted.
+    ///
+    /// Emitting the header is deferred by one line so that, if that next
+    /// line is a hunk header, the header's own hyperlink can point at the
+    /// same line as the hunk header's hyperlink.
+    header_rendered: bool,
 }
 
 /// Remove trailing diff timestamp from a string, retaining only the filename
@@ -51,7 +59,9 @@
                 self.url = hyperlink_filename(without_timestamp(new_name));
                 return Ok(Response {
                     line_accepted: LineAcceptance::AcceptedWantMore,
-                    highlighted: 
vec![StringFuture::from_string(self.highlighted())],
+                    // The header itself is rendered from the next line, once
+                    // we know whether it's a hunk header to link to.
+                    highlighted: vec![],
                 });
             }
             return Err("--- was not followed by +++".to_string());
@@ -59,9 +69,42 @@
 
         // We are now past the --- / +++ headers and are dealing with the body
         let mut highlights: Vec<StringFuture> = Vec::new();
+
+        if !self.header_rendered {
+            // This is the first body line. Parse it as a hunk header once,
+            // both to pick the header's link line and, if it is one, to
+            // become the hunk sub-highlighter below (avoiding parsing it
+            // twice).
+            let parsed_hunk_header = HunkHeader::parse(line);
+            let first_hunk_line = match &parsed_hunk_header {
+                Some(hunk_header) => Some(hunk_header.first_modified_line()?),
+                None => None,
+            };
+            
highlights.push(StringFuture::from_string(self.highlighted(first_hunk_line)));
+            self.header_rendered = true;
+
+            return match parsed_hunk_header {
+                Some(hunk_header) => {
+                    self.sub_highlighter = 
Some(Box::new(HunkLinesHighlighter::from_parsed(
+                        hunk_header,
+                        self.formatter.clone(),
+                        &self.url,
+                    )?));
+                    Ok(Response {
+                        line_accepted: LineAcceptance::AcceptedWantMore,
+                        highlighted: highlights,
+                    })
+                }
+                None => Ok(Response {
+                    line_accepted: LineAcceptance::RejectedDone,
+                    highlighted: highlights,
+                }),
+            };
+        }
+
         if let Some(ref mut highlighter) = self.sub_highlighter {
             let resp = highlighter.consume_line(line, thread_pool)?;
-            highlights = resp.highlighted;
+            highlights.extend(resp.highlighted);
             match resp.line_accepted {
                 LineAcceptance::AcceptedWantMore => {
                     return Ok(Response {
@@ -106,10 +149,20 @@
         if self.new_name.is_empty() {
             return Err("Input ended early, --- should have been followed by 
+++".to_string());
         }
+
+        let mut return_me = vec![];
+        if !self.header_rendered {
+            // No hunk ever showed up, e.g. an empty file being added/deleted,
+            // or a truncated diff. Fall back to a plain, line-less link.
+            return_me.push(StringFuture::from_string(self.highlighted(None)));
+            self.header_rendered = true;
+        }
+
         if let Some(ref mut sub) = self.sub_highlighter {
-            return sub.consume_eof(thread_pool);
+            return_me.extend(sub.consume_eof(thread_pool)?);
         }
-        Ok(vec![])
+
+        Ok(return_me)
     }
 }
 
@@ -131,12 +184,13 @@
             formatter,
             sub_highlighter: None,
             url: None, // Will be set in consume_line() based on the +++ line
+            header_rendered: false,
         };
 
         return Some(highlighter);
     }
 
-    fn highlighted(&self) -> String {
+    fn highlighted(&self, first_hunk_line: Option<usize>) -> String {
         let (mut old_tokens, mut new_tokens) = diff(&self.old_name, 
&self.new_name);
 
         // New file
@@ -165,7 +219,7 @@
             None
         };
 
-        decorate_paths(&mut old_tokens, &mut new_tokens);
+        decorate_paths(&mut old_tokens, &mut new_tokens, first_hunk_line);
 
         if let Some(prefix) = new_prefix {
             new_tokens.insert(0, prefix);
@@ -230,7 +284,11 @@
     return None;
 }
 
-fn hyperlink_tokenized(just_path: &mut [StyledToken], just_filename: &mut 
[StyledToken]) {
+fn hyperlink_tokenized(
+    just_path: &mut [StyledToken],
+    just_filename: &mut [StyledToken],
+    fragment_line: Option<usize>,
+) {
     // Convert filename_tokens into a String
     let mut filename = String::new();
     for token in just_path.iter() {
@@ -240,7 +298,11 @@
         filename.push_str(&token.token);
     }
 
-    if let Some(url) = hyperlink_filename(&filename) {
+    if let Some(mut url) = hyperlink_filename(&filename) {
+        if let Some(fragment_line) = fragment_line {
+            url.set_fragment(Some(&fragment_line.to_string()));
+        }
+
         // Actually link the tokens
         for token in just_path.iter_mut() {
             token.url = Some(url.clone());
@@ -401,7 +463,11 @@
     return (old_has_git_prefix || old_is_absolute) && (new_has_git_prefix || 
new_is_absolute);
 }
 
-pub(crate) fn decorate_paths(old_tokens: &mut [StyledToken], new_tokens: &mut 
[StyledToken]) {
+pub(crate) fn decorate_paths(
+    old_tokens: &mut [StyledToken],
+    new_tokens: &mut [StyledToken],
+    fragment_line: Option<usize>,
+) {
     let look_for_git_prefixes = have_git_prefixes(old_tokens, new_tokens);
 
     let old_split = split_row(look_for_git_prefixes, old_tokens);
@@ -425,9 +491,9 @@
     if old_split.just_path == new_split.just_path
         && old_split.just_filename == new_split.just_filename
     {
-        hyperlink_tokenized(old_split.just_path, old_split.just_filename);
+        hyperlink_tokenized(old_split.just_path, old_split.just_filename, 
fragment_line);
     }
-    hyperlink_tokenized(new_split.just_path, new_split.just_filename);
+    hyperlink_tokenized(new_split.just_path, new_split.just_filename, 
fragment_line);
 
     lowlight_dev_null(old_split.just_path, old_split.just_filename);
     lowlight_dev_null(new_split.just_path, new_split.just_filename);
@@ -471,13 +537,18 @@
 
     use crate::refiner::tests::FORMATTER;
     fn highlight_header_lines(old_line: &str, new_line: &str) -> String {
+        let thread_pool = ThreadPool::new(1);
         let mut test_me = FileHighlighter::from_line(old_line, 
FORMATTER.clone()).unwrap();
-        let response = test_me.consume_line(new_line, 
&ThreadPool::new(1)).unwrap();
+        let response = test_me.consume_line(new_line, &thread_pool).unwrap();
         assert_eq!(LineAcceptance::AcceptedWantMore, response.line_accepted);
-        assert_eq!(1, response.highlighted.len());
+        assert_eq!(0, response.highlighted.len());
 
-        let highlighted = response
-            .highlighted
+        // The header is rendered once we know whether the next line is a
+        // hunk header. These tests have no next line, so EOF flushes it.
+        let eof_highlighted = test_me.consume_eof(&thread_pool).unwrap();
+        assert_eq!(1, eof_highlighted.len());
+
+        let highlighted = eof_highlighted
             .into_iter()
             .next()
             .unwrap()
@@ -524,6 +595,32 @@
         assert_eq!(canonical, expected, "Hyperlink should point to README.md");
     }
 
+    /// Clicking the `--- ` / `+++ ` header should land in the same place as
+    /// clicking the first section header below it.
+    #[test]
+    fn test_header_links_to_first_hunk_line() {
+        let thread_pool = ThreadPool::new(1);
+        let mut test_me = FileHighlighter::from_line("--- README.md", 
FORMATTER.clone()).unwrap();
+
+        test_me.consume_line("+++ README.md", &thread_pool).unwrap();
+        let response = test_me
+            .consume_line("@@ -10,3 +10,3 @@ Some section", &thread_pool)
+            .unwrap();
+
+        let mut highlighted = String::new();
+        for mut future in response.highlighted {
+            highlighted.push_str(future.get());
+        }
+
+        // The section header links to the start line plus three context lines,
+        // and the file header should agree with it.
+        assert!(
+            highlighted.contains("README.md#13"),
+            "Expected a hyperlink to line 13 of README.md, got: {:?}",
+            highlighted
+        );
+    }
+
     #[test]
     fn test_brighten_filename() {
         let highlighted = highlight_header_lines("--- a/x/y/z.txt", "+++ 
b/x/y/z.txt");
@@ -595,7 +692,7 @@
         let mut row = vec![StyledToken::new("README.md".to_string(), 
Style::Context)];
 
         // Act: call the function
-        hyperlink_tokenized(&mut [], &mut row);
+        hyperlink_tokenized(&mut [], &mut row, None);
 
         // Assert: the file:/// URL points to our README.md file
         let url = row[0].url.as_ref().expect("Token should have a URL");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/src/hunk_header.rs 
new/riff-3.6.2/src/hunk_header.rs
--- old/riff-3.6.1/src/hunk_header.rs   2025-11-17 20:58:21.000000000 +0100
+++ new/riff-3.6.2/src/hunk_header.rs   2026-08-15 23:27:15.000000000 +0200
@@ -120,6 +120,20 @@
         })
     }
 
+    /// The first modified line in the new file, i.e. the line a click on this
+    /// hunk's title should land on.
+    pub fn first_modified_line(&self) -> Result<usize, String> {
+        // Skip this number of leading context lines. There are usually three
+        // context lines. If people start complaining we'll have to detect the
+        // actual number.
+        let context_lines_skip = 3;
+        if let Some(last_start) = self.starts.last().cloned() {
+            return Ok(last_start + context_lines_skip);
+        }
+
+        return Err(format!("HunkHeader has no start lines: {:?}", self));
+    }
+
     /// Render into an ANSI highlighted string, not ending in a newline.
     pub fn render(&self, url: &Option<url::Url>) -> Result<String, String> {
         let mut rendered = String::new();
@@ -145,19 +159,7 @@
         if let Some(title) = &self.title {
             rendered.push(' ');
             rendered.push_str(BOLD);
-            if let Some(last_start) = self.starts.last().cloned() {
-                // Skip this number of context lines to end up at the first
-                // modified line. There are usually three context lines. If
-                // people start complaining we'll have to detect the actual
-                // number.
-                let context_lines_skip = 3;
-                rendered.push_str(&hyperlink(title, url, last_start + 
context_lines_skip));
-            } else {
-                return Err(format!(
-                    "HunkHeader has no start lines when rendering title: {:?}",
-                    self
-                ));
-            }
+            rendered.push_str(&hyperlink(title, url, 
self.first_modified_line()?));
         }
 
         rendered.push_str(NORMAL);
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/src/hunk_highlighter.rs 
new/riff-3.6.2/src/hunk_highlighter.rs
--- old/riff-3.6.1/src/hunk_highlighter.rs      2025-11-17 20:58:21.000000000 
+0100
+++ new/riff-3.6.2/src/hunk_highlighter.rs      2026-08-15 23:27:15.000000000 
+0200
@@ -98,18 +98,28 @@
         Self: Sized,
     {
         if let Some(hunk_header) = HunkHeader::parse(line) {
-            return Ok(Some(HunkLinesHighlighter {
-                hunk_header: Some(hunk_header.render(file_url)?),
-                remaining_line_counts: hunk_header.linecounts.clone(),
-                initial_line_counts: hunk_header.linecounts,
-                lines_highlighter: None,
-                formatter,
-            }));
+            return Ok(Some(Self::from_parsed(hunk_header, formatter, 
file_url)?));
         }
 
         return Ok(None);
     }
 
+    /// Like `from_line()`, but for a hunk header the caller has already
+    /// parsed, so the same line doesn't get parsed twice.
+    pub(crate) fn from_parsed(
+        hunk_header: HunkHeader,
+        formatter: Formatter,
+        file_url: &Option<url::Url>,
+    ) -> Result<Self, String> {
+        return Ok(HunkLinesHighlighter {
+            hunk_header: Some(hunk_header.render(file_url)?),
+            remaining_line_counts: hunk_header.linecounts.clone(),
+            initial_line_counts: hunk_header.linecounts,
+            lines_highlighter: None,
+            formatter,
+        });
+    }
+
     fn consume_line_internal(
         &mut self,
         line: &str,
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/src/line_collector.rs 
new/riff-3.6.2/src/line_collector.rs
--- old/riff-3.6.1/src/line_collector.rs        2025-11-17 20:58:21.000000000 
+0100
+++ new/riff-3.6.2/src/line_collector.rs        2026-08-15 23:27:15.000000000 
+0200
@@ -111,12 +111,8 @@
         // Flush outstanding lines
         self.drain_plain();
 
-        if self.lines_highlighter.is_some() {
-            let result = self
-                .lines_highlighter
-                .as_mut()
-                .unwrap()
-                .consume_eof(&self.thread_pool);
+        if let Some(lines_highlighter) = self.lines_highlighter.as_mut() {
+            let result = lines_highlighter.consume_eof(&self.thread_pool);
             if let Err(error) = result {
                 self.lines_highlighter = None;
                 eprintln!("ERROR at end of input: {error}");
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.1/src/rename_highlighter.rs 
new/riff-3.6.2/src/rename_highlighter.rs
--- old/riff-3.6.1/src/rename_highlighter.rs    2025-11-17 20:58:21.000000000 
+0100
+++ new/riff-3.6.2/src/rename_highlighter.rs    2026-08-15 23:27:15.000000000 
+0200
@@ -60,7 +60,7 @@
         let old_filename = render(&LINE_STYLE_OLD_FILENAME, "", &old_tokens);
         let new_filename = render(&LINE_STYLE_NEW_FILENAME, "", &new_tokens);
 
-        decorate_paths(&mut old_tokens, &mut new_tokens);
+        decorate_paths(&mut old_tokens, &mut new_tokens, None);
 
         return format!(
             "rename from {old_filename}{NORMAL}\n{BOLD}rename to 
{new_filename}{NORMAL}\n"

++++++ vendor.tar.zst ++++++
/work/SRC/openSUSE:Factory/riff/vendor.tar.zst 
/work/SRC/openSUSE:Factory/.riff.new.1258/vendor.tar.zst differ: char 7, line 1

Reply via email to