Skip to content

Commit

Permalink
Merge pull request #1238 from Barsik-sus/willbe_better_diff
Browse files Browse the repository at this point in the history
READY : (willbe): Update diff functionality and improve detail
  • Loading branch information
Wandalen authored Mar 25, 2024
2 parents 79984cc + cbf642d commit 9043c5c
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 42 deletions.
1 change: 1 addition & 0 deletions module/move/willbe/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ petgraph = "~0.6"
ptree = "~0.4"
rayon = "1.8.0"
semver = "~1.0.0"
similar = "~2.4"
regex = "1.10.2"
sha-1 = "~0.10"
tar = "~0.4"
Expand Down
8 changes: 7 additions & 1 deletion module/move/willbe/src/action/publish_diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ mod private
#[ cfg_attr( feature = "tracing", tracing::instrument ) ]
pub fn publish_diff( o : PublishDiffOptions ) -> Result< DiffReport >
{
// These files are ignored because they can be safely changed without affecting functionality
//
// - `.cargo_vcs_info.json` - contains the git sha1 hash that varies between different commits
// - `Cargo.toml.orig` - can be safely modified because it is used to generate the `Cargo.toml` file automatically, and the `Cargo.toml` file is sufficient to check for changes
const IGNORE_LIST : [ &str; 2 ] = [ ".cargo_vcs_info.json", "Cargo.toml.orig" ];

let path = AbsolutePath::try_from( o.path )?;
let dir = CrateDir::try_from( path )?;

Expand All @@ -48,7 +54,7 @@ mod private
}
}

Ok( crate_diff( &l, &r ) )
Ok( crate_diff( &l, &r ).exclude( IGNORE_LIST ) )
}
}

Expand Down
143 changes: 124 additions & 19 deletions module/move/willbe/src/entity/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,60 +8,147 @@ mod private
fmt::Formatter,
path::PathBuf,
};
use std::collections::HashMap;
use colored::Colorize;
use crates_tools::CrateArchive;
use similar::*;

use wtools::iter::Itertools;


/// The `Diff` enum is designed to represent differences between two versions
/// of some kind of item identified.
#[ derive( Debug ) ]
#[ derive( Debug, Clone ) ]
pub enum Diff< T >
{
/// This variant represents items that are identical or same in both versions.
Same( T ),
/// This variant represents items that exists in both versions but have been modified.
Modified( T ),
/// This variant represents items that were added.
Add( T ),
/// This variant represents items that were removed.
Rem( T ),
}

/// The `DiffItem` enum is designed to represent differences between two versions
/// of an item. It contains two variants `File` and `Content`.
#[ derive( Debug, Clone ) ]
pub enum DiffItem
{
/// - `File(Diff<()>)`: Represents differences in the file itself. The `Diff` enum
/// contains three possible variants `Same`, `Add`, and `Rem`. Each variant of `Diff`
/// represents the status of the file.
/// - `Same(())`: Represents that the file is identical or the same in both versions.
/// - `Add(())`: Represents that the file was added in the new version.
/// - `Rem(())`: Represents that the file was removed in the new version.
File( Diff< () > ),
/// - `Content(Vec<Diff<String>>): Represents differences in the content of the item.
/// The `Diff` enum inside `Vec` represents differences in strings present in the file.
/// The `Diff` enum contains three possible variants `Same`, `Add`, and `Rem`. Each variant
/// of `Diff` represents the status of the string.
/// - `Same(String)`: Represents that the string is identical or the same in both versions.
/// - `Add(String)`: Represents that the string was added in the new version.
/// - `Rem(String)`: Represents that the string was removed in the new version.
Content( Vec< Diff< String > > ),
}

/// The `DiffReport` struct represents a diff report containing a list of `Diff` objects.
#[ derive( Debug, Default ) ]
pub struct DiffReport( Vec< Diff< PathBuf > > );
#[ derive( Debug, Default, Clone ) ]
pub struct DiffReport( pub( crate ) HashMap< PathBuf, DiffItem > );

impl DiffReport
{
/// Excludes specified items from a report.
///
/// # Arguments
///
/// * `items` - A collection of items to exclude. This can be any type that can be converted into a `HashSet` of `PathBuf` objects.
///
/// # Returns
///
/// Returns a new instance of the struct with the excluded items removed from the internal report.
pub fn exclude< Is, I >( mut self, items : Is ) -> Self
where
Is : Into< HashSet< I > >,
I : AsRef< std::path::Path >,
{
let current = self.0.keys().cloned().collect::< HashSet< _ > >();
let Some( key ) = current.iter().next() else { return self };

let crate_part = std::path::Path::new( key.components().next().unwrap().as_os_str() );
let excluded_paths = items.into().into_iter().map( | i | crate_part.join( i ) ).collect();

let map = current.difference( &excluded_paths ).filter_map( | key | self.0.remove_entry( key ) ).collect();

Self( map )
}

/// Checks if there are any changes in the DiffItems.
///
/// # Returns
/// * `true` if there are changes in any of the DiffItems.
/// * `false` if all DiffItems are the same.
pub fn has_changes( &self ) -> bool
{
!self.0.iter().all( |( _, item )| matches!( item, DiffItem::File( Diff::Same( () ) ) ))
}
}

impl std::fmt::Display for DiffReport
{
fn fmt( &self, f : &mut Formatter< '_ > ) -> std::fmt::Result
{
for diff in self.0.iter()
.sorted_by_key( | d | match d { Diff::Modified( df ) | Diff::Same( df ) | Diff::Rem( df ) | Diff::Add( df ) => df } )
for ( path , diff ) in self.0.iter().sorted_by_key( |( k, _ )| k.as_path() )
{
match diff
{
Diff::Same( t ) => writeln!( f, "{}", t.display() )?,
Diff::Modified( t ) => writeln!( f, "~ {}", t.to_string_lossy().yellow() )?,
Diff::Add( t ) => writeln!( f, "+ {}", t.to_string_lossy().green() )?,
Diff::Rem( t ) => writeln!( f, "- {}", t.to_string_lossy().red() )?,
DiffItem::File( item ) =>
{
match item
{
Diff::Same( _ ) => writeln!( f, " {}", path.display() )?,
Diff::Add( _ ) => writeln!( f, "+ {} NEW", path.to_string_lossy().green() )?,
Diff::Rem( _ ) => writeln!( f, "- {} REMOVED", path.to_string_lossy().red() )?,
};
}
DiffItem::Content( items ) =>
{
let path = path.to_string_lossy();
let len = path.len() + "~ MODIFIED".len();
writeln!( f, "~ {} MODIFIED", path.yellow() )?;
writeln!( f, "{}", "=".repeat( len + 2 ) )?;
for item in items
{
match item
{
Diff::Same( t ) => write!( f, "| {}", t )?,
Diff::Add( t ) => write!( f, "| + {}", t.green() )?,
Diff::Rem( t ) => write!( f, "| - {}", t.red() )?,
};
}
writeln!( f, "{}", "=".repeat( len + 2 ) )?;
}
};
}

Ok( () )
}
}

/// Compare two crate archives and create a difference report.
/// Creates a differential report between two crate archives.
///
/// This function compares two crate archives and generates a report (`DiffReport`),
/// indicating the discrepancies between them.
///
/// # Arguments
///
/// * `left` - A reference to the left crate archive.
/// * `right` - A reference to the right crate archive.
/// * `left`: A reference to the first crate archive.
/// Changes that are present here but lacking in 'right' are classified as additions.
/// * `right`: A reference to the second crate archive.
/// Changes not found in 'left' but present in 'right' are classified as removals.
///
/// # Returns
///
/// A `DiffReport` struct representing the difference between the two crate archives.
/// A `DiffReport` struct, representing the unique and shared attributes of the two crate archives.
pub fn crate_diff( left : &CrateArchive, right : &CrateArchive ) -> DiffReport
{
let mut report = DiffReport::default();
Expand All @@ -75,12 +162,12 @@ mod private

for &path in local_only
{
report.0.push( Diff::Add( path.to_path_buf() ) );
report.0.insert( path.to_path_buf(), DiffItem::File( Diff::Add( () ) ) );
}

for &path in remote_only
{
report.0.push( Diff::Rem( path.to_path_buf() ) );
report.0.insert( path.to_path_buf(), DiffItem::File( Diff::Rem( () ) ) );
}

for &path in both
Expand All @@ -91,11 +178,28 @@ mod private

if local == remote
{
report.0.push( Diff::Same( path.to_path_buf() ) );
report.0.insert( path.to_path_buf(), DiffItem::File( Diff::Same( () ) ) );
}
else
{
report.0.push( Diff::Modified( path.to_path_buf() ) );
let mut items = vec![];
let local_str = String::from_utf8_lossy( local );
let remote_str = String::from_utf8_lossy( remote );
let diff = TextDiff::from_lines( &remote_str, &local_str );
for hunk in diff.unified_diff().context_radius( 5 ).iter_hunks()
{
for change in hunk.iter_changes()
{
let item = match change.tag()
{
ChangeTag::Delete => Diff::Rem( change.to_string() ),
ChangeTag::Insert => Diff::Add( change.to_string() ),
ChangeTag::Equal => Diff::Same( change.to_string() ),
};
items.push( item );
}
}
report.0.insert( path.to_path_buf(), DiffItem::Content( items ) );
}
}

Expand All @@ -108,6 +212,7 @@ mod private
crate::mod_interface!
{
protected use Diff;
protected use DiffItem;
protected use DiffReport;
protected use crate_diff;
}
25 changes: 3 additions & 22 deletions module/move/willbe/src/entity/package.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ mod private
use action::readme_health_table_renew::Stability;
use former::Former;
use workspace::WorkspacePackage;
use diff::crate_diff;

///
#[ derive( Debug, Clone ) ]
Expand Down Expand Up @@ -883,7 +884,7 @@ mod private
//
// - `.cargo_vcs_info.json` - contains the git sha1 hash that varies between different commits
// - `Cargo.toml.orig` - can be safely modified because it is used to generate the `Cargo.toml` file automatically, and the `Cargo.toml` file is sufficient to check for changes
const IGNORE_LIST : [ &str; 2 ] = [ ".cargo_vcs_info.json", "Cargo.toml.orig" ];
const IGNORE_LIST : [ &str; 2 ] = [ ".cargo_vcs_info.json", "Cargo.toml" ];

let name = package.name()?;
let version = package.version()?;
Expand All @@ -902,27 +903,7 @@ mod private
_ => return Err( PackageError::LoadRemotePackage ),
};

let filter_ignore_list = | p : &&Path | !IGNORE_LIST.contains( &p.file_name().unwrap().to_string_lossy().as_ref() );
let local_package_files : Vec< _ > = local_package.list().into_iter().filter( filter_ignore_list ).sorted().collect();
let remote_package_files : Vec< _ > = remote_package.list().into_iter().filter( filter_ignore_list ).sorted().collect();

if local_package_files != remote_package_files { return Ok( true ); }

let mut is_same = true;
for path in local_package_files
{
// unwraps is safe because the paths to the files was compared previously
let local = local_package.content_bytes( path ).unwrap();
let remote = remote_package.content_bytes( path ).unwrap();
// if local != remote
// {
// println!( "local :\n===\n{}\n===\nremote :\n===\n{}\n===", String::from_utf8_lossy( local ), String::from_utf8_lossy( remote ) );
// }

is_same &= local == remote;
}

Ok( !is_same )
Ok( crate_diff( &local_package, &remote_package ).exclude( IGNORE_LIST ).has_changes() )
}
}

Expand Down

0 comments on commit 9043c5c

Please sign in to comment.