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-11-09 16:07:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/riff (Old)
 and      /work/SRC/openSUSE:Factory/.riff.new.3759290 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "riff"

Mon Nov  9 16:07:54 2026 rev:30 rq:1396297 version:3.7.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/riff/riff.changes        2026-08-17 
17:02:51.284219112 +0200
+++ /work/SRC/openSUSE:Factory/.riff.new.3759290/riff.changes   2026-11-09 
16:08:55.210563845 +0100
@@ -1,0 +2,10 @@
+Mon Nov  9 09:58:57 UTC 2026 - Michael Vetter <[email protected]>
+
+- Update to 3.7.0:
+  Handle jj diff --tool riff better:
+  * jj's left/ and right/ path prefixes are no longer highlighted as changes in
+  j file headers
+  * Hyperlinks now point to your real working copy files, not to jj's
+    temporary diff directory
+
+-------------------------------------------------------------------

Old:
----
  riff-3.6.2.tar.zst

New:
----
  riff-3.7.0.tar.zst

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

Other differences:
------------------
++++++ riff.spec ++++++
--- /var/tmp/diff_new_pack.5GADN7/_old  2026-11-09 16:08:56.381612899 +0100
+++ /var/tmp/diff_new_pack.5GADN7/_new  2026-11-09 16:08:56.382612941 +0100
@@ -17,7 +17,7 @@
 
 
 Name:           riff
-Version:        3.6.2
+Version:        3.7.0
 Release:        0
 Summary:        A diff filter highlighting which line parts have changed
 License:        MIT

++++++ _service ++++++
--- /var/tmp/diff_new_pack.5GADN7/_old  2026-11-09 16:08:56.415614323 +0100
+++ /var/tmp/diff_new_pack.5GADN7/_new  2026-11-09 16:08:56.419614491 +0100
@@ -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.2</param>
+    <param name="revision">3.7.0</param>
     <param name="match-tag">*</param>
     <param name="versionrewrite-pattern">v(\d+\.\d+\.\d+)</param>
     <param name="versionrewrite-replacement">\1</param>

++++++ riff-3.6.2.tar.zst -> riff-3.7.0.tar.zst ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.2/Cargo.lock new/riff-3.7.0/Cargo.lock
--- old/riff-3.6.2/Cargo.lock   2026-08-15 23:27:15.000000000 +0200
+++ new/riff-3.7.0/Cargo.lock   2026-11-06 20:32:11.000000000 +0100
@@ -557,7 +557,7 @@
 
 [[package]]
 name = "riffdiff"
-version = "3.6.2"
+version = "3.7.0"
 dependencies = [
  "backtrace",
  "base64",
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.2/Cargo.toml new/riff-3.7.0/Cargo.toml
--- old/riff-3.6.2/Cargo.toml   2026-08-15 23:27:15.000000000 +0200
+++ new/riff-3.7.0/Cargo.toml   2026-11-06 20:32:11.000000000 +0100
@@ -2,7 +2,7 @@
 
 [package]
 name = "riffdiff"                                             # Actually 
"riff", but that was already taken on crates.io
-version = "3.6.2"
+version = "3.7.0"
 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.2/ci.sh new/riff-3.7.0/ci.sh
--- old/riff-3.6.2/ci.sh        2026-08-15 23:27:15.000000000 +0200
+++ new/riff-3.7.0/ci.sh        2026-11-06 20:32:11.000000000 +0100
@@ -16,7 +16,7 @@
 rustup component add clippy rustfmt
 
 # Settings are at the top of main.rs
-cargo clippy
+cargo clippy --all-targets
 
 # Copied from here:
 # <https://docs.travis-ci.com/user/languages/rust/#default-build-script>
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.2/src/file_highlighter.rs 
new/riff-3.7.0/src/file_highlighter.rs
--- old/riff-3.6.2/src/file_highlighter.rs      2026-08-15 23:27:15.000000000 
+0200
+++ new/riff-3.7.0/src/file_highlighter.rs      2026-11-06 20:32:11.000000000 
+0100
@@ -14,6 +14,13 @@
 use crate::hunk_highlighter::HunkLinesHighlighter;
 use crate::refiner::Formatter;
 
+/// Old / new path prefixes we lowlight in file headers, and strip when looking
+/// for files to hyperlink.
+///
+/// - `a` / `b` are from `git diff`
+/// - `left` / `right` are from `jj diff --tool riff`
+const PATH_PREFIX_PAIRS: &[(&str, &str)] = &[("a", "b"), ("left", "right")];
+
 pub(crate) struct FileHighlighter {
     /// May or may not end with one or more tabs + a timestamp string.
     old_name: String,
@@ -242,30 +249,57 @@
     }
 }
 
+/// Directories to resolve relative paths against, in order of preference.
+///
+/// `$PWD` goes first because `jj diff --tool riff` runs us with cwd in a
+/// temporary directory that jj deletes when we exit, while leaving `$PWD`
+/// pointing at the user's directory. Linking into the temporary directory
+/// would give dead links.
+///
+/// The cwd is the fallback for when `$PWD` is unset, or has gone stale for 
some
+/// other reason. `cargo test` from a subdirectory does that for example.
+fn base_dirs() -> Vec<std::path::PathBuf> {
+    let mut base_dirs = Vec::new();
+
+    if let Some(pwd) = std::env::var_os("PWD") {
+        let pwd = std::path::PathBuf::from(pwd);
+        if pwd.is_absolute() {
+            base_dirs.push(pwd);
+        }
+    }
+
+    // Empty means relative to the cwd, even if we can't tell what it is
+    base_dirs.push(std::env::current_dir().unwrap_or_default());
+
+    return base_dirs;
+}
+
 /// This function will try to find the file, either by the passed-in name or by
-/// stripping a/ or b/ prefixes.
+/// stripping any of the `PATH_PREFIX_PAIRS` prefixes.
+///
+/// Relative paths are looked up in each of the `base_dirs()`, trying all
+/// candidates in one directory before moving on to the next.
 fn hyperlink_filename(filename: &str) -> Option<url::Url> {
     let mut raw_candidates: Vec<&str> = Vec::new();
     raw_candidates.push(filename);
-    if let Some(no_a_prefix) = filename.strip_prefix("a/") {
-        raw_candidates.push(no_a_prefix);
-    }
-    if let Some(no_b_prefix) = filename.strip_prefix("b/") {
-        raw_candidates.push(no_b_prefix);
-    }
-
-    for candidate in raw_candidates {
-        if candidate == "/dev/null" {
-            continue;
+    for prefix in PATH_PREFIX_PAIRS.iter().flat_map(|(old, new)| [old, new]) {
+        if let Some(without_prefix) = filename
+            .strip_prefix(prefix)
+            .and_then(|rest| rest.strip_prefix('/'))
+        {
+            raw_candidates.push(without_prefix);
         }
+    }
 
-        let mut path = std::path::PathBuf::from(candidate);
-        if !path.is_absolute() {
-            if let Ok(cwd) = std::env::current_dir() {
-                path = cwd.join(&path);
-            }
-        }
+    let paths = base_dirs().into_iter().flat_map(|base_dir| {
+        raw_candidates
+            .iter()
+            .filter(|candidate| **candidate != "/dev/null")
+            // Joining an absolute path just returns that absolute path
+            .map(move |candidate| base_dir.join(candidate))
+    });
 
+    for path in paths {
         if !path.exists() {
             continue;
         }
@@ -379,7 +413,7 @@
 
 /// Splits a row into (prefix, just_path, full_path, just_filename, timestamp) 
slices.
 ///
-/// Let's say we have a row like this and `look_for_git_prefixes` is true:
+/// Let's say we have a row like this and `look_for_path_prefixes` is true:
 /// ```
 /// a/doc/c.txt\t2023-12-15 15:43:29
 /// ```
@@ -390,13 +424,14 @@
 /// - `just_filename`: `c.txt`
 /// - `time_space`: `\t`
 /// - `timestamp`: `2023-12-15 15:43:29`
-fn split_row<'a>(look_for_git_prefixes: bool, row: &'a mut [StyledToken]) -> 
SplitRow<'a> {
-    let path_start = if look_for_git_prefixes
-        && row.len() >= 2
-        && row[0].token.len() == 1 // "a" or "b"
-        && (row[1].token == "/" || row[1].token == 
std::path::MAIN_SEPARATOR.to_string())
-    {
-        // If we have a git prefix, the path starts after the first two tokens
+fn split_row<'a>(look_for_path_prefixes: bool, row: &'a mut [StyledToken]) -> 
SplitRow<'a> {
+    let is_any_prefix = |tokens: &[StyledToken]| {
+        PATH_PREFIX_PAIRS
+            .iter()
+            .any(|(old, new)| starts_with_prefix(tokens, old) || 
starts_with_prefix(tokens, new))
+    };
+    let path_start = if look_for_path_prefixes && is_any_prefix(row) {
+        // If we have a path prefix, the path starts after the first two tokens
         2
     } else {
         // Otherwise, it starts at the first token
@@ -443,24 +478,26 @@
     };
 }
 
-fn have_git_prefixes(old_tokens: &[StyledToken], new_tokens: &[StyledToken]) 
-> bool {
-    // Both "a/..." and "/dev/null" count as having a git prefix.
+fn is_separator(token: &StyledToken) -> bool {
+    return token.token == "/" || token.token == 
std::path::MAIN_SEPARATOR.to_string();
+}
+
+/// True if `tokens` start with `prefix` followed by a path separator
+fn starts_with_prefix(tokens: &[StyledToken], prefix: &str) -> bool {
+    return tokens.len() >= 2 && tokens[0].token == prefix && 
is_separator(&tokens[1]);
+}
 
-    let old_has_git_prefix = old_tokens.len() >= 2
-        && old_tokens[0].token == "a"
-        && (old_tokens[1].token == "/"
-            || old_tokens[1].token == std::path::MAIN_SEPARATOR.to_string());
-    let old_is_absolute = !old_tokens.is_empty() && old_tokens[0].token == "/"
-        || old_tokens[0].token == std::path::MAIN_SEPARATOR.to_string();
-
-    let new_has_git_prefix = new_tokens.len() >= 2
-        && new_tokens[0].token == "b"
-        && (new_tokens[1].token == "/"
-            || new_tokens[1].token == std::path::MAIN_SEPARATOR.to_string());
-    let new_is_absolute = !new_tokens.is_empty() && new_tokens[0].token == "/"
-        || new_tokens[0].token == std::path::MAIN_SEPARATOR.to_string();
+/// True if both paths have matching prefixes from `PATH_PREFIX_PAIRS`, like
+/// "a/..." and "b/...". An absolute path, like "/dev/null", matches any
+/// prefix.
+fn have_path_prefixes(old_tokens: &[StyledToken], new_tokens: &[StyledToken]) 
-> bool {
+    let old_is_absolute = old_tokens.first().is_some_and(is_separator);
+    let new_is_absolute = new_tokens.first().is_some_and(is_separator);
 
-    return (old_has_git_prefix || old_is_absolute) && (new_has_git_prefix || 
new_is_absolute);
+    return PATH_PREFIX_PAIRS.iter().any(|(old_prefix, new_prefix)| {
+        (old_is_absolute || starts_with_prefix(old_tokens, old_prefix))
+            && (new_is_absolute || starts_with_prefix(new_tokens, new_prefix))
+    });
 }
 
 pub(crate) fn decorate_paths(
@@ -468,10 +505,10 @@
     new_tokens: &mut [StyledToken],
     fragment_line: Option<usize>,
 ) {
-    let look_for_git_prefixes = have_git_prefixes(old_tokens, new_tokens);
+    let look_for_path_prefixes = have_path_prefixes(old_tokens, new_tokens);
 
-    let old_split = split_row(look_for_git_prefixes, old_tokens);
-    let new_split = split_row(look_for_git_prefixes, new_tokens);
+    let old_split = split_row(look_for_path_prefixes, old_tokens);
+    let new_split = split_row(look_for_path_prefixes, new_tokens);
 
     // Brighten file names
     old_split.just_filename.iter_mut().for_each(|token| {
@@ -514,7 +551,7 @@
         token.style = Style::Lowlighted;
     });
 
-    // Lowlight git prefixes
+    // Lowlight path prefixes
     old_split.prefix.iter_mut().for_each(|token| {
         token.style = Style::Lowlighted;
     });
@@ -635,6 +672,35 @@
     }
 
     #[test]
+    fn test_brighten_filename_left_right_prefixes() {
+        // jj uses left/ and right/ when running riff as its diff tool:
+        // https://github.com/walles/riff/issues/82
+        let highlighted = highlight_header_lines("--- left/x/y/z.txt", "+++ 
right/x/y/z.txt");
+        assert_eq!(
+            format!(
+                "\
+                {BOLD}--- 
{NORMAL_INTENSITY}{FAINT}left/{NORMAL}x/y/{BOLD}z.txt{NORMAL}\n\
+                {BOLD}+++ 
{NORMAL_INTENSITY}{FAINT}right/{NORMAL}x/y/{BOLD}z.txt{NORMAL}\n"
+            ),
+            highlighted
+        );
+    }
+
+    #[test]
+    fn test_mismatched_prefixes_are_highlighted() {
+        // a/ only goes with b/, and left/ only goes with right/
+        let highlighted = highlight_header_lines("--- a/z.txt", "+++ 
right/z.txt");
+        assert_eq!(
+            format!(
+                "\
+                {BOLD}--- 
{INVERSE_VIDEO}{NORMAL_INTENSITY}{OLD}a{NORMAL}/{BOLD}z.txt{NORMAL}\n\
+                {BOLD}+++ 
{INVERSE_VIDEO}{NORMAL_INTENSITY}{GREEN}right{NORMAL}/{BOLD}z.txt{NORMAL}\n"
+            ),
+            highlighted
+        );
+    }
+
+    #[test]
     fn test_brighten_filename_without_path() {
         let highlighted = highlight_header_lines("--- z.txt", "+++ z.txt");
         assert_eq!(
@@ -767,6 +833,36 @@
     }
 
     #[test]
+    fn test_hyperlink_filename_right_prefix() {
+        let url_opt = super::hyperlink_filename("right/README.md");
+
+        let path_from_url = url_opt
+            .expect("Expected Some(url::Url) for right/ prefixed path")
+            .to_file_path()
+            .expect("Should convert URL back to path");
+        let expected = std::fs::canonicalize("README.md").expect("Canonicalize 
README.md");
+        let actual = 
std::fs::canonicalize(&path_from_url).expect("Canonicalize URL path");
+        assert_eq!(actual, expected, "Canonical paths must match");
+    }
+
+    #[test]
+    fn test_split_row_with_multi_character_prefix() {
+        let mut tokens: Vec<StyledToken> = ["left", "/", "doc", "/", "c.txt"]
+            .iter()
+            .map(|s| StyledToken::new(s.to_string(), Style::Context))
+            .collect();
+        let split = split_row(true, &mut tokens);
+        assert_eq!(
+            split.prefix.iter().map(|t| &t.token).collect::<Vec<_>>(),
+            ["left", "/"]
+        );
+        assert_eq!(
+            split.just_path.iter().map(|t| &t.token).collect::<Vec<_>>(),
+            ["doc", "/"]
+        );
+    }
+
+    #[test]
     fn test_split_row_with_slash_separator() {
         let mut tokens: Vec<StyledToken> = ["doc", "/", "c.txt", "\t", 
"2023-12-15 15:43:29"]
             .iter()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.2/src/token_collector.rs 
new/riff-3.7.0/src/token_collector.rs
--- old/riff-3.6.2/src/token_collector.rs       2026-08-15 23:27:15.000000000 
+0200
+++ new/riff-3.7.0/src/token_collector.rs       2026-11-06 20:32:11.000000000 
+0100
@@ -252,7 +252,7 @@
     #[test]
     fn test_basic() {
         let rendered = render(
-            &*LINE_STYLE_NEW,
+            &LINE_STYLE_NEW,
             "+",
             &[
                 StyledToken {
@@ -274,7 +274,7 @@
     fn test_removed_trailing_whitespace() {
         // It shouldn't be highlighted, just added ones should
         let actual = render(
-            &*LINE_STYLE_OLD,
+            &LINE_STYLE_OLD,
             "-",
             &[StyledToken::new(" ".to_string(), Style::DiffPartMidlighted)],
         );
@@ -286,7 +286,7 @@
     fn test_removed_nonleading_tab() {
         // It shouldn't be highlighted, just added ones should
         let actual = render(
-            &*LINE_STYLE_OLD,
+            &LINE_STYLE_OLD,
             "-",
             &[
                 StyledToken::new("x".to_string(), Style::DiffPartMidlighted),
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.2/testdata/jj-left-right.diff 
new/riff-3.7.0/testdata/jj-left-right.diff
--- old/riff-3.6.2/testdata/jj-left-right.diff  1970-01-01 01:00:00.000000000 
+0100
+++ new/riff-3.7.0/testdata/jj-left-right.diff  2026-11-06 20:32:11.000000000 
+0100
@@ -0,0 +1,7 @@
+diff -ur --show-c-function --new-file left/docs/greeting.txt 
right/docs/greeting.txt
+--- left/docs/greeting.txt     2026-11-05 19:26:54.000000000 +0100
++++ right/docs/greeting.txt    2026-11-05 19:26:54.000000000 +0100
+@@ -1,2 +1,2 @@
+ Hello
+-World
++there
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.2/testdata/jj-left-right.riff-output 
new/riff-3.7.0/testdata/jj-left-right.riff-output
--- old/riff-3.6.2/testdata/jj-left-right.riff-output   1970-01-01 
01:00:00.000000000 +0100
+++ new/riff-3.7.0/testdata/jj-left-right.riff-output   2026-11-06 
20:32:11.000000000 +0100
@@ -0,0 +1,7 @@
+diff -ur --show-c-function --new-file left/docs/greeting.txt 
right/docs/greeting.txt
+--- left/docs/greeting.txt   2026-11-05 
19:26:54.000000000 +0100
++++ right/docs/greeting.txt  2026-11-05 
19:26:54.000000000 +0100
+@@ -1,2 +1,2 @@
+ Hello
+-World
++there
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/riff-3.6.2/tests/pwd_hyperlinks.rs 
new/riff-3.7.0/tests/pwd_hyperlinks.rs
--- old/riff-3.6.2/tests/pwd_hyperlinks.rs      1970-01-01 01:00:00.000000000 
+0100
+++ new/riff-3.7.0/tests/pwd_hyperlinks.rs      2026-11-06 20:32:11.000000000 
+0100
@@ -0,0 +1,94 @@
+// jj runs diff tools in a temporary directory containing left/ and right/
+// copies of the trees, deleting that directory when the tool exits. It leaves
+// PWD pointing at the user's directory though, so that's where hyperlinks
+// should point.
+
+#![cfg(unix)]
+
+use std::io::Write;
+use std::path::Path;
+use std::process::{Command, Stdio};
+
+const OLD_C: &str = "int main() {\n  1;\n  2;\n  3;\n  4;\n  5;\n}\n";
+const NEW_C: &str = "int main() {\n  1;\n  2;\n  3;\n  4;\n  50;\n}\n";
+
+fn file_url(path: &Path) -> String {
+    let canonical = std::fs::canonicalize(path).expect("Path should 
canonicalize");
+    format!("file://{}", canonical.display())
+}
+
+fn write(path: &Path, contents: &str) {
+    std::fs::create_dir_all(path.parent().unwrap()).unwrap();
+    std::fs::write(path, contents).unwrap();
+}
+
+#[test]
+fn test_jj_diff_tool_links_to_pwd_not_temp_dir() {
+    let repo = tempfile::tempdir().unwrap();
+    write(&repo.path().join("m.c"), NEW_C);
+
+    let jj_temp = tempfile::tempdir().unwrap();
+    write(&jj_temp.path().join("left/m.c"), OLD_C);
+    write(&jj_temp.path().join("right/m.c"), NEW_C);
+
+    // This is what jj does: cwd is the temp dir, PWD is the user's directory
+    let output = Command::new(env!("CARGO_BIN_EXE_riff"))
+        .args(["--color=on", "--no-pager", "left", "right"])
+        .current_dir(jj_temp.path())
+        .env("PWD", repo.path())
+        .output()
+        .unwrap();
+    let stdout = String::from_utf8(output.stdout).unwrap();
+
+    let repo_url = file_url(&repo.path().join("m.c"));
+    assert!(
+        stdout.contains(&format!("{}#6", repo_url)),
+        "Expected a link to {}#6 in:\n{}",
+        repo_url,
+        stdout
+    );
+
+    let temp_dir_url = file_url(jj_temp.path());
+    assert!(
+        !stdout.contains(&temp_dir_url),
+        "Expected no links into {} in:\n{}",
+        temp_dir_url,
+        stdout
+    );
+}
+
+#[test]
+fn test_links_relative_to_cwd_when_not_found_under_pwd() {
+    // This is what "cargo test" does when run from a subdirectory: PWD is the
+    // subdirectory, cwd is the package root.
+    let cwd = tempfile::tempdir().unwrap();
+    write(&cwd.path().join("m.c"), NEW_C);
+    let pwd = tempfile::tempdir().unwrap();
+
+    let diff = "--- a/m.c\n+++ b/m.c\n@@ -2,6 +2,6 @@ int main() {\n   1;\n   
2;\n   3;\n   4;\n-  5;\n+  50;\n }\n";
+
+    let mut child = Command::new(env!("CARGO_BIN_EXE_riff"))
+        .args(["--color=on", "--no-pager"])
+        .current_dir(cwd.path())
+        .env("PWD", pwd.path())
+        .stdin(Stdio::piped())
+        .stdout(Stdio::piped())
+        .spawn()
+        .unwrap();
+    child
+        .stdin
+        .take()
+        .unwrap()
+        .write_all(diff.as_bytes())
+        .unwrap();
+    let output = child.wait_with_output().unwrap();
+    let stdout = String::from_utf8(output.stdout).unwrap();
+
+    let cwd_url = file_url(&cwd.path().join("m.c"));
+    assert!(
+        stdout.contains(&cwd_url),
+        "Expected a link to {} in:\n{}",
+        cwd_url,
+        stdout
+    );
+}

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

Reply via email to