From d6358dd3be5a9af41e9553b9c3d77d07a24c30ee Mon Sep 17 00:00:00 2001 From: earsernoob Date: Sat, 21 Jun 2025 18:09:44 +0800 Subject: [PATCH 1/4] feat(libra/diff): support imara_diff v0.2.0 and add the extra param --algorithm of diff command --- Cargo.toml | 2 +- common/src/config.rs | 10 +-- libra/Cargo.toml | 2 +- libra/src/command/diff.rs | 143 ++++++++++++++++++++++++++++++++++---- 4 files changed, 138 insertions(+), 19 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1f33269cc..78355a962 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -113,7 +113,7 @@ http = "1.3.1" byte-unit = "5.1.6" color-backtrace = "0.7.0" ignore = "0.4.23" -imara-diff = "0.1.8" +imara-diff = "0.2.0" indicatif = "0.17.11" infer = "0.19.0" path-absolutize = "3.1.1" diff --git a/common/src/config.rs b/common/src/config.rs index c5ee494f1..e6f151a71 100644 --- a/common/src/config.rs +++ b/common/src/config.rs @@ -1,16 +1,16 @@ //! Configuration management for the Mono and Mega application //! This module provides functionality to load, parse, and manage configuration settings -use std::rc::Rc; -use std::path::PathBuf; use std::cell::RefCell; use std::collections::HashMap; +use std::path::PathBuf; +use std::rc::Rc; -pub use config as c; use c::{ConfigError, FileFormat}; +pub use config as c; -use config::{Source, ValueKind}; use config::builder::DefaultState; +use config::{Source, ValueKind}; use serde::{Deserialize, Deserializer, Serialize}; use callisto::sea_orm_active_enums::StorageTypeEnum; @@ -77,7 +77,7 @@ pub fn mega_cache() -> PathBuf { .unwrap() .to_string() }); - + PathBuf::from(cache_dir) } diff --git a/libra/Cargo.toml b/libra/Cargo.toml index f7ef5d299..a5d38cd3d 100644 --- a/libra/Cargo.toml +++ b/libra/Cargo.toml @@ -22,7 +22,7 @@ futures = { workspace = true } futures-util = { workspace = true } gemini = { workspace = true, optional = true } hex = { workspace = true } -imara-diff = { workspace = true } +imara-diff.workspace = true indicatif = { workspace = true } infer = { workspace = true } lazy_static = { workspace = true } diff --git a/libra/src/command/diff.rs b/libra/src/command/diff.rs index 20ff119bf..440f1b92c 100644 --- a/libra/src/command/diff.rs +++ b/libra/src/command/diff.rs @@ -6,7 +6,7 @@ use std::{ }; use clap::Parser; -use imara_diff::{intern::InternedInput, Algorithm, UnifiedDiffBuilder}; +use imara_diff::{Algorithm, BasicLineDiffPrinter, Diff, InternedInput, UnifiedDiffConfig}; use mercury::{ hash::SHA1, internal::{ @@ -50,6 +50,11 @@ pub struct DiffArgs { #[clap(help = "Files to compare")] pathspec: Vec, + /// choose the exact diff algorithm default value is histogram + /// support myers and myersMinimal + #[clap(long, default_value = "histogram", value_parser=["histogram", "myers", "myersMinimal"])] + pub algorithm: Option, + // Print the result to file #[clap(long, value_name = "FILENAME")] pub output: Option, @@ -124,7 +129,14 @@ pub async fn execute(args: DiffArgs) { let mut buf: Vec = Vec::new(); // filter files, cross old and new files, and pathspec - diff(old_blobs, new_blobs, paths.into_iter().collect(), &mut buf).await; + diff( + old_blobs, + new_blobs, + args.algorithm.unwrap_or_default(), + paths.into_iter().collect(), + &mut buf, + ) + .await; match w { Some(ref mut file) => { @@ -155,6 +167,7 @@ pub async fn execute(args: DiffArgs) { pub async fn diff( old_blobs: Vec<(PathBuf, SHA1)>, new_blobs: Vec<(PathBuf, SHA1)>, + algorithm: String, filter: Vec, w: &mut dyn io::Write, ) { @@ -252,7 +265,7 @@ pub async fn diff( }; writeln!(w, "--- {}", old_prefix).unwrap(); writeln!(w, "+++ {}", new_prefix).unwrap(); - imara_diff_result(&old_text, &new_text, w); + imara_diff_result(&old_text, &new_text, algorithm.as_str(), w); } _ => { // TODO: Handle non-UTF-8 data as binary for now; consider optimization in the future. @@ -352,14 +365,31 @@ fn similar_diff_result(old: &str, new: &str, w: &mut dyn io::Write) { } } -fn imara_diff_result(old: &str, new: &str, w: &mut dyn io::Write) { +fn imara_diff_result(old: &str, new: &str, algorithm: &str, w: &mut dyn io::Write) { let input = InternedInput::new(old, new); - let diff = imara_diff::diff( - Algorithm::Histogram, - &input, - UnifiedDiffBuilder::new(&input), - ); - write!(w, "{}", diff).unwrap(); + + let algo = match algorithm { + "myers" => Algorithm::Myers, + "myersMinimal" => Algorithm::MyersMinimal, + // default is the histogram algo + _ => Algorithm::Histogram, + }; + tracing::debug!("libra [diff]: choose the algorithm: {:?}", algo); + + let mut diff = Diff::compute(algo, &input); + + // did the postprocess_lines + diff.postprocess_lines(&input); + + let result = diff + .unified_diff( + &BasicLineDiffPrinter(&input.interner), + UnifiedDiffConfig::default(), + &input, + ) + .to_string(); + + write!(w, "{}", result).unwrap(); } #[cfg(test)] @@ -375,10 +405,9 @@ mod test { /// Verifies parameter requirements, conflicts and default values are handled correctly. fn test_args() { { - let args = DiffArgs::try_parse_from(["diff", "--old", "old", "--new", "new", "paths"]); + let args = DiffArgs::try_parse_from(["$iff", "--old", "old", "--new", "new", "paths"]); assert!(args.is_ok()); let args = args.unwrap(); - // println!("{:?}", args); assert_eq!(args.old, Some("old".to_string())); assert_eq!(args.new, Some("new".to_string())); assert_eq!(args.pathspec, vec!["paths".to_string()]); @@ -407,6 +436,26 @@ mod test { assert!(args.is_err()); assert!(args.err().unwrap().kind() == clap::error::ErrorKind::MissingRequiredArgument); } + { + // --algorithm arg + let args = DiffArgs::try_parse_from([ + "diff", + "--old", + "old", + "--new", + "new", + "--algorithm", + "myers", + "target paths", + ]) + .unwrap(); + assert_eq!(args.algorithm, Some("myers".to_string())); + } + { + // --algorithm arg with default value + let args = DiffArgs::try_parse_from(["diff", "--old", "old", "target paths"]).unwrap(); + assert_eq!(args.algorithm, Some("histogram".to_string())); + } } #[test] @@ -440,4 +489,74 @@ mod test { assert_eq!(blob.len(), 1); assert_eq!(blob[0].0, PathBuf::from("not_ignore")); } + + #[test] + fn test_diff_algorithms_correctness_and_efficiency() { + let old = r#"function foo() { + if (condition) { + doSomething(); + doSomethingElse(); + andAnotherThing(); + } else { + alternative(); + } +}"#; + + let new = r#"function foo() { + if (condition) { + // Added comment + doSomething(); + // Modified this line + modifiedSomethingElse(); + andAnotherThing(); + } else { + alternative(); + } + + // Added new block + addedNewFunctionality(); +}"#; + let mut outputs = Vec::new(); + + let algos = ["histogram", "myers", "myersMinimal"]; + + // test the different algo benchmark + for algo in algos { + let mut buf = Vec::new(); + let start = Instant::now(); + imara_diff_result(old, new, algo, &mut buf); + let elapse = start.elapsed(); + let ouput = String::from_utf8(buf).expect("Invalid UTF-8 in diff ouput"); + + println!("libra diff algorithm: {:?} Spend Time: {:?}", algo, elapse); + assert!( + !ouput.is_empty(), + "libra diff algorithm: {} produce a empty output", + algo + ); + assert!( + ouput.contains("@@"), + "libra diff algorithm: {}, ouput missing diff markers", + algo + ); + + outputs.push((algo, ouput)); + } + + // check the line counter difference + for (algo, output) in outputs { + let plus_line = output.lines().filter(|line| line.starts_with("+")).count(); + let minus_line = output.lines().filter(|line| line.starts_with("-")).count(); + assert_eq!( + plus_line, 6, + "libra diff algorithm {}, expect plus_line: 6, got {} ", + algo, plus_line + ); + assert_eq!( + minus_line, 1, + "libra diff algorithm {}, expect minus_line: 1, got {} ", + algo, minus_line + ); + } + } } From c9322df7282334387f1fc77fe888afa5cfe02b19 Mon Sep 17 00:00:00 2001 From: earsernoob Date: Sat, 21 Jun 2025 19:18:18 +0800 Subject: [PATCH 2/4] fix(libra/diff): fix the unsual imports to pass CI/CD --- libra/src/command/diff.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/libra/src/command/diff.rs b/libra/src/command/diff.rs index 440f1b92c..f180452c0 100644 --- a/libra/src/command/diff.rs +++ b/libra/src/command/diff.rs @@ -397,6 +397,7 @@ mod test { use crate::utils::test; use serial_test::serial; use std::fs; + use std::time::Instant; use tempfile::tempdir; use super::*; From 53e0b06f80a654ca88ef58910f5b505dacdaa00a Mon Sep 17 00:00:00 2001 From: earsernoob Date: Mon, 23 Jun 2025 10:31:50 +0800 Subject: [PATCH 3/4] fix typo --- libra/src/command/diff.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libra/src/command/diff.rs b/libra/src/command/diff.rs index f180452c0..b81e47f8b 100644 --- a/libra/src/command/diff.rs +++ b/libra/src/command/diff.rs @@ -406,7 +406,7 @@ mod test { /// Verifies parameter requirements, conflicts and default values are handled correctly. fn test_args() { { - let args = DiffArgs::try_parse_from(["$iff", "--old", "old", "--new", "new", "paths"]); + let args = DiffArgs::try_parse_from(["diff", "--old", "old", "--new", "new", "paths"]); assert!(args.is_ok()); let args = args.unwrap(); assert_eq!(args.old, Some("old".to_string())); From 1228847bfdca4dc732f144a740ed48aa60bc1193 Mon Sep 17 00:00:00 2001 From: earsernoob Date: Mon, 23 Jun 2025 10:52:52 +0800 Subject: [PATCH 4/4] chore: dependenc imports pattern uniformed --- libra/Cargo.toml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libra/Cargo.toml b/libra/Cargo.toml index a5d38cd3d..400e9a115 100644 --- a/libra/Cargo.toml +++ b/libra/Cargo.toml @@ -22,7 +22,7 @@ futures = { workspace = true } futures-util = { workspace = true } gemini = { workspace = true, optional = true } hex = { workspace = true } -imara-diff.workspace = true +imara-diff = { workspace = true } indicatif = { workspace = true } infer = { workspace = true } lazy_static = { workspace = true } @@ -46,7 +46,7 @@ serde_json = { workspace = true } sha1 = { workspace = true } similar = { workspace = true } tokio = { workspace = true, features = ["rt-multi-thread", "rt", "macros"] } -tokio-util = {workspace = true, features = ["io"] } +tokio-util = { workspace = true, features = ["io"] } tracing = { workspace = true } tracing-subscriber = { workspace = true } wax = { workspace = true } @@ -60,5 +60,8 @@ pager = { workspace = true } tempfile = { workspace = true } serial_test = { workspace = true } tokio = { workspace = true, features = ["macros", "process"] } -testcontainers = { workspace = true, features = ["http_wait","reusable-containers"] } +testcontainers = { workspace = true, features = [ + "http_wait", + "reusable-containers", +] } reqwest = { workspace = true, features = ["blocking"] }